#!/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.
#
# =========
#
# Startup script for Fuseki under *nix systems (works with cygwin too)
#
# Configuration
# -------------
# Values are loaded from /etc/default/fuseki, if it exists.
# The description below cover the default settings if /etc/default/fuseki
# does not exist.
#
# Set DEBUG=1 (see below or set the environment variable) to print the
# settings to be used.
#
# JAVA
#   Command to invoke Java. If not set, java (from the PATH) will be used.
#   JAVA_HOME can also be used to set JAVA.
#
# JAVA_OPTIONS
#   Extra options to pass to the JVM.
#
# FUSEKI_HOME
#   Where Fuseki is installed.  If not set, the script will try
#   to guess it based on the script invokation path.
# 
# FUSEKI_BASE
#   The root of the runtime area - logs files, system files, local configuration.
#   Defaults to $FUSEKI_HOME/run.
#
# FUSEKI_RUN
#   Where the fuseki.pid file should be stored.  It defaults
#   first available of /var/run, /usr/var/run, $FUSEKI_HOME and /tmp if not set.
#
# FUSEKI_PID
#   The FUSEKI PID file, defaults to $FUSEKI_RUN/fuseki.pid
#
# FUSEKI_ARGS
#   The arguments to pass to the Fuseki server on the command line. Defaults to:
#                                        # if FUSEKI_CONF is not set
#    --config=$FUSEKI_CONF               # if FUSEKI_CONF is set
#
# FUSEKI_START
#   Path to the jar file. Defaults to $FUSEKI_HOME/fuseki-server.jar 
#
# FUSEKI_CLASSES
#   Path to extra jars to add to the class path.  Defaults to none
#   Should be of the form path/class.jar:path/class2.jar
#
# FUSEKI_CONF
#   The Fuseki configuration file, usually in RDF Turtle notation.
#
# FUSEKI_USER
#   If set, the server will be run as this user
#
# FUSEKI_LOGS
#   Directory where logs will be generated. 
#   Fixed as $FUSEKI_BASE/logs.
#
# FUSEKI_LOGS_STDERROUT
#   Log file with stderr and stdout log output from Fuseki. 
#   Defaults to $FUSEKI_LOGS/stderrout.log


### BEGIN INIT INFO
# Provides:          fuseki
# Required-Start:    $remote_fs $network
# Required-Stop:     $remote_fs $network
# Default-Start:     3 4 5
# Default-Stop:      0 1 2 6
# Short-Description: Start Jena Fuseki at boot time
# Description:       Jena Fuseki is a service that provides a SPARQL API over HTTP
### END INIT INFO

# DEBUG=1
NAME=fuseki
if [ -f /etc/default/$NAME ]; then
  . /etc/default/$NAME
fi

# simple replacements for LSB daemon logging functions if not defined
# Centos 6 has init-functions but not log_daemon_msg/log_begin_msg/log_end_msg
# Define simple version - attempt to replace with /lib/lsb/init-functions
log_daemon_msg() {
    echo $1
}
log_begin_msg() {
    echo $1
}
log_end_msg() {
    if [ $1 -eq 0 ]; then
	echo '[OK]'
    else
	echo '[failed]'
    fi
}

if [ -f /lib/lsb/init-functions ]; then
    . /lib/lsb/init-functions
fi

usage()
{
  echo "Usage: ${0##*/} {start|stop|restart|run|status}"
  exit 1
}

[ $# -gt 0 ] || usage
CMD="$1"

# Utility functions

findDirectory()
{
  local L OP=$1
  shift
  for L in "$@"; do
    [ "$OP" "$L" ] || continue
    printf %s "$L"
    break
  done
}

findFile()
{
  local L F=$1
  shift
  for L in "$@"; do
    [ -f "${L}/${F}" ] || continue
    printf %s "${L}/${F}"
    break
  done
}

running()
{
  local PID=$(cat "$1" 2>/dev/null) || return 1
  ps -p "$PID" >/dev/null 2>&1
}

# Are we running in cygwin?
cygwin=false
case "`uname`" in
    CYGWIN*) cygwin=true;;
esac

# Set FUSKEI_HOME to the script invocation directory if it is not specified
if [ -z "$FUSEKI_HOME" ]
then
  SCRIPT="$0"
  # Catch common issue: script has been symlinked
  if [ -L "$SCRIPT" ]
  then
    SCRIPT="$(readlink "$0")"
    # If link is relative
    case "$SCRIPT" in
      /*) ;; # fine
      *) SCRIPT=$( dirname "$0" )/$SCRIPT;; # fix
    esac
  fi
  # Work out root from script location
  FUSEKI_HOME="$( cd "$( dirname "$SCRIPT" )" && pwd )"
fi

# Deal with Cygwin path issues
if [ "$cygwin" == "true" ]
then
  FUSEKI_HOME=`cygpath -w "$FUSEKI_HOME"`
fi

if [ ! -e "$FUSEKI_HOME" ]
then
  log_daemon_msg "FUSEKI_HOME '$FUSEKI_HOME' does not exist" 1>&2
  exit 1
fi

# Set FUSEKI_BASE
if [ -z "$FUSEKI_BASE" ]
then
  #FUSEKI_BASE="/etc/fuseki"
  FUSEKI_BASE="$FUSEKI_HOME/run"
  # Autocreate FUSEKI_BASE if defaulting to FUSEKI_HOME/run (simple deployment)
  if [ ! -e "$FUSEKI_BASE" ]
  then
      log_daemon_msg "Creating '$FUSEKI_BASE'"
      if [ "$cygwin" == "true" ]
      then
	  FUSEKI_BASE=`cygpath -w "$FUSEKI_BASE"`
      fi
      mkdir "$FUSEKI_BASE"
  fi
fi

if [ "$cygwin" == "true" ]
then
  FUSEKI_BASE=`cygpath -w "$FUSEKI_BASE"`
fi

if [ ! -e "$FUSEKI_BASE" -o ! -d "$FUSEKI_BASE" ]
then
  log_daemon_msg "FUSEKI_BASE '$FUSEKI_BASE' does not exist or is not a directory" 1>&2
  exit 1
fi

if [ ! -w "$FUSEKI_BASE" ]
then
  log_daemon_msg "FUSEKI_BASE '$FUSEKI_BASE' is not writable." 1>&2
  exit 1
fi

# Find a location for the pid file
if [ -z "$FUSEKI_RUN" ]
then
  FUSEKI_RUN=$(findDirectory -w /var/run /usr/var/run $FUSEKI_HOME /tmp)
fi

# Get PID file name
if [ -z "$FUSEKI_PID" ]
then
  FUSEKI_PID="$FUSEKI_RUN/fuseki.pid"
fi

# Log directory
if [ -n "$FUSEKI_LOGS" ]
then
    log_daemon_msg "FUSEKI_LOGS can not be set externally - ignored" 1>&2 
fi
FUSEKI_LOGS="$FUSEKI_BASE/logs"

# Stderr and stdout log
if [ -z "$FUSEKI_LOGS_STDERROUT" ]
then
  FUSEKI_LOGS_STDERROUT="$FUSEKI_LOGS/stderrout.log"
fi

# Set up JAVA if not set
if [ -z "$JAVA" ]
then
    if [ -z "$JAVA_HOME" ]
    then
       JAVA=$(which java)
    else
        JAVA=$JAVA_HOME/bin/java
    fi
fi

if [ -z "$JAVA" ]
then
    (
	echo "Cannot find a Java JDK."
	echo "Please set either set JAVA or JAVA_HOME and put java (>=1.8) in your PATH."
    ) 1>&2
    exit 1
fi

# The location of the start up JAR
FUSEKI_START=${FUSEKI_START:-$FUSEKI_HOME/fuseki-server.jar}

# Deal with Cygwin path issues
if [ "$cygwin" == "true" ]
then
  FUSEKI_START=`cygpath -w "$FUSEKI_START"`
fi

# Some JVM settings
if [ -z "$JAVA_OPTIONS" ]
then
  JAVA_OPTIONS="-Xmx1500M"
fi

# Default Fuseki Arguments
if [ -z "$FUSEKI_ARGS" ]
then
  if [ -z "$FUSEKI_CONF" ]
  then
    FUSEKI_ARGS=""
  else
    FUSEKI_ARGS="--config=$FUSEKI_CONF"
  fi
fi

# Run command

if [ -z "$FUSEKI_CLASSES" ]
then
  RUN_ARGS=(${JAVA_OPTIONS[@]} -jar "$FUSEKI_START" "${FUSEKI_ADDITIONAL_ARGS[@]}" $FUSEKI_ARGS)
else
  RUN_ARGS=(${JAVA_OPTIONS[@]} -cp "$FUSEKI_START:$FUSEKI_CLASSES" org.apache.jena.fuseki.cmd.FusekiCmd "${FUSEKI_ADDITIONAL_ARGS[@]}" $FUSEKI_ARGS)
fi
RUN_CMD=("$JAVA" "${RUN_ARGS[@]}")

# Export the variables to be seen by the java server process.
export FUSEKI_HOME
export FUSEKI_BASE

#####################################################
# Comment these out after you're happy with what
# the script is doing.
#####################################################
if (( DEBUG ))
then
  log_daemon_msg "FUSEKI_HOME    =  $FUSEKI_HOME"
  log_daemon_msg "FUSEKI_BASE    =  $FUSEKI_BASE"
  log_daemon_msg "FUSEKI_CONF    =  $FUSEKI_CONF"
  log_daemon_msg "FUSEKI_RUN     =  $FUSEKI_RUN"
  log_daemon_msg "FUSEKI_PID     =  $FUSEKI_PID"
  log_daemon_msg "FUSEKI_ARGS    =  $FUSEKI_ARGS"
  log_daemon_msg "FUSEKI_START   =  $FUSEKI_START"
  log_daemon_msg "FUSEKI_CLASSES =  $FUSEKI_CLASSES"
  log_daemon_msg "CONFIGS        =  ${CONFIGS[*]}"
  log_daemon_msg "JAVA           =  $JAVA"
  log_daemon_msg "JAVA_OPTIONS   =  ${JAVA_OPTIONS[*]}"
  log_daemon_msg "RUN_ARGS       =  ${RUN_ARGS[@]}"
  log_daemon_msg "RUN_CMD        =  ${RUN_CMD[@]}"
  log_daemon_msg "Stdout/stderr  =  $FUSEKI_LOGS_STDERROUT"
fi

NO_START=${NO_START:-0}

# Life cycle functions
start() {
  if (( NO_START )); then
    log_daemon_msg "Not starting Fuseki - NO_START=1"
    exit
  fi

  # Make sure the data and log directories exist
  mkdir -p "$FUSEKI_LOGS"
  if [ ! -z "$FUSEKI_USER" ]
  then
    chown "$FUSEKI_USER" "$FUSEKI_LOGS"
  fi

  # Make sure the .jar file exists
  if [ ! -e $FUSEKI_START ]; then
    log_daemon_msg "Could not see Fuseki .jar file: \$FUSEKI_START has value '$FUSEKI_START'"
    exit 1
  fi

  log_begin_msg "Starting Fuseki"
  if type start-stop-daemon > /dev/null 2>&1
  then
    unset CH_USER
    if [ -n "$FUSEKI_USER" ]
    then
      CH_USER="--chuid $FUSEKI_USER"
    fi
    if start-stop-daemon --start $CH_USER --chdir "$FUSEKI_HOME" --background --make-pidfile --pidfile "$FUSEKI_PID" --startas /bin/bash -- -c "exec $JAVA ${RUN_ARGS[*]} > $FUSEKI_LOGS_STDERROUT 2>&1"
    then
      sleep 2
      if running "$FUSEKI_PID"
      then
        log_end_msg 0
        print_started
      else
        log_end_msg 1
      fi
    else
      log_end_msg 1
      log_daemon_msg "** start-stop-daemon failed to run"
    fi
  else
    if running $FUSEKI_PID
    then
      log_end_msg 1
      log_daemon_msg 'Already Running!'
      exit 1
    else
      # dead pid file - remove
      rm -f "$FUSEKI_PID"
    fi

# use subshell to cd to FUSKEI_HOME directory and execute
    (cd "$FUSEKI_HOME"
    if [ "$FUSEKI_USER" ]
    then
      touch "$FUSEKI_PID"
      chown "$FUSEKI_USER" "$FUSEKI_PID"
##       if [  "$FUSEKI_LOGS_STDERROUT" != "$FUSEKI_LOGS/stderrout.log" ]]
##       then
## 	  log_daemon_msg "Redirecting Fuseki stderr/stdout to $FUSEKI_LOGS_STDERROUT"
##       fi
      ## su with login, passing over environment variables.
      su - "$FUSEKI_USER" -c "
        export FUSEKI_BASE='${FUSEKI_BASE}'
        export FUSEKI_HOME='${FUSEKI_HOME}'
        exec ${RUN_CMD[*]} &> '$FUSEKI_LOGS_STDERROUT' &
        disown \$!
        echo \$! > '$FUSEKI_PID'"
    else
      exec "${RUN_CMD[@]}" &> "$FUSEKI_LOGS_STDERROUT" &
      disown $!
      echo $! > "$FUSEKI_PID"
    fi
)
    
    log_end_msg 0
    print_started
  fi
}

print_started() {
  log_daemon_msg "STARTED Fuseki `date`"
  log_daemon_msg "PID=$(cat "$FUSEKI_PID" 2>/dev/null)"
}

delete_fuseki_pid_file() {
  rm -f "$FUSEKI_PID"
}

stop() {
  log_begin_msg "Stopping Fuseki: "

  if ! running "$FUSEKI_PID"
  then
    log_end_msg 1

    # if a stop rather than a restart, signal failure to stop
    if [ -z "$1" ]
    then
      exit 1
    fi
  fi

  ###############################################################
  # !!!! This code needs to be improved, too many repeats !!!!  #
  ###############################################################
  if type start-stop-daemon > /dev/null 2>&1; then
    start-stop-daemon --stop --pidfile "$FUSEKI_PID" --chdir "$FUSEKI_HOME" --startas "$JAVA" --signal HUP

    ## Die after a 30 second timeout
    TIMEOUT=30
    while running "$FUSEKI_PID"; do
      if (( TIMEOUT-- == 0 )); then
        start-stop-daemon --stop --pidfile "$FUSEKI_PID" --chdir "$FUSEKI_HOME" --startas "$JAVA" --signal KILL
      fi
        sleep 1
    done
    delete_fuseki_pid_file
    log_end_msg 0
  else
    PID=$(cat "$FUSEKI_PID" 2>/dev/null)
    kill "$PID" 2>/dev/null

    TIMEOUT=30
    while running $FUSEKI_PID; do
      if (( TIMEOUT-- == 0 )); then
        kill -KILL "$PID" 2>/dev/null
      fi
      sleep 1
    done
    delete_fuseki_pid_file
    log_end_msg 0
  fi
}


# Run in the foreground, as the current user
run() {
  # Make sure the .jar file exists
  if [ ! -e $FUSEKI_START ]; then
    log_daemon_msg "Could not see Fuseki .jar file: \$FUSEKI_START has value '$FUSEKI_START'"
    exit 1
  fi
  exec "${RUN_CMD[@]}"
}

case $CMD in
  start)
    start
  ;;
  stop)
    stop
  ;;
  restart)
    stop "restarting"
    start
  ;;
  run)
    run
  ;;
  status)
    if running $FUSEKI_PID
    then
      PID=`cat "$FUSEKI_PID"`
      log_daemon_msg "Fuseki is running with pid: $PID"
    else
      log_daemon_msg "Fuseki is not running"
    fi
  ;;
  *)
    usage
  ;;
esac

exit 0
