#! /bin/bash

###
# ============LICENSE_START=======================================================
# ONAP POLICY
# ================================================================================
# Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
# ================================================================================
# Licensed 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.
# ============LICENSE_END=========================================================
##

# #############################################################
# Features Directory Layout:
#
# POLICY_HOME/
#   L─ features/
#        L─ <feature-name>*/
#            L─ [config]/
#            |   L─ <config-file>+
#            L─ lib/
#            |   L─ [dependencies]/
#            |   |   L─ <dependent-jar>+
#            │   L─ feature/
#            │       L─ <feature-jar>
#            L─ [db]/
#            │   L─ <db-name>/+
#            │       L─ sql/
#            │           L─ <sql-scripts>*
#            L─ [install]
#                L─ [enable]
#                L─ [disable]
#                L─ [other-directories-or-files]
#
# notes:  [] = optional , * = 0 or more , + = 1 or more
#   <feature-name> directory without "feature-" prefix.
#   [config]       feature configuration directory that contains all configuration
#                  needed for this features
#   [config]/<config-file>  preferable named with "feature-<feature-name>" prefix to
#                  precisely match it against the exact features, source code, and 
#                  associated wiki page for configuration details.
#   lib            jar libraries needed by this features
#   lib/[dependencies]  3rd party jar dependencies not provided by base installation
#                  of pdp-d that are necessary for <feature-name> to operate
#                  correctly.
#   lib/feature    the single feature jar that implements the feature.
#   [db]           database directory, if the feature contains sql.
#   [db]/<db-name> database to which underlying sql scripts should be applied against.
#                  ideally, <db-name> = <feature-name> so it is easily to associate
#                  the db data with a feature itself.   Ideally, since a feature is
#                  a somewhat independent isolated unit of functionality,the <db-name>
#                  database ideally isolates all its data.
#   [db]/<db-name>/sql  directory with all the sql scripts.
#   [db]/<db-name>/sql/<sql-scripts>  for this feature sql scripts
#                  upgrade scripts should be suffixed with ".upgrade.sql"
#                  downgrade scripts should be suffixed with ".downgrade.sql"
#   [install]      custom installation directory where custom enable or disable scripts
#                  and other free form data is included to be used for the enable and 
#                  and disable scripts.
#   [install]/[enable] enable script executed when the enable operation is invoked in
#                  the feature.
#   [install]/[disable] disable script executed when the disable operation is invoked in
#                  the feature.
#   [install]/[other-directories-or-files] other executables, or data that can be used
#                  by the feature for any of its operations.   The content is determined
#                  by the feature designer.
#  
# Operations:
#   install: installs a feature
#   uninstall: uninstalls a feature
#   enable : enables 1) dependencies, 2) configuration, 3) database, 4) feature, 5) customization
#   disable: disables 1) dependencies, 2) configuration, 3) database, 4) feature, 6) customization
#   status : status of a feature
#
# 'enable' operation details:
#  0. Validates current state before the operation is committed
#  1. sets the symbolic link to the actual feature jar in pdp-d classpath ($POLICY_HOME/lib)
#  2. sets symbolic links to feature dependencies in pdp-d classpath ($POLICY_HOME/lib)
#  3. sets symbolic links to feature configuration in pdp-d configuration directory ($POLICY_HOME/config)
#  4. sets symbolic links to feature upgrade scripts and removes links to downgrade scripts (if any) 
#     in the pdp-d migration directory ($POLICY_HOME/etc/db/migration).
#  5. cd to the feature 'install' directory an executes (if exists) the 'enable' script to allow for specific
#     customizations for this feature.
#
# 'disable' operation details:
#  0. Validates current state before the operation is committed
#  1. removes the symbolic link to the actual feature jar in pdp-d classpath ($POLICY_HOME/lib)
#  2. removes symbolic links to feature dependencies in pdp-d classpath ($POLICY_HOME/lib)
#  3. removes symbolic links to feature configuration in pdp-d configuration directory ($POLICY_HOME/config)
#  4. removes symbolic links to feature upgrade scripts and sets links to downgrade scripts (if any) 
#     in the pdp-d migration directory ($POLICY_HOME/etc/db/migration).
#  5. cd to the feature 'install' directory an executes (if exists) the 'disable' script to allow for specific
#     customizations for this feature.
# 
# Notes for DB enabled features:
# 	A. Upgrade/Downgrade SQL File Name Format:  
# 			<VERSION>-<pdp|feature-name>[-description](.upgrade|.downgrade).sql
#   B. See related tooling: db-migrator and policy
#
# Example:
#
# POLICY_HOME/
#   L─ features/
#        L── eelf/
#        │   L── config/
#        │   │   L── logback-eelf.xml
#        │   L─ lib/
#        │   │   L─ dependencies/
#        │   │   │   L─ ONAP-Logging-1.1.0-SNAPSHOT.jar
#        │   │   │   L─ eelf-core-1.0.0.jar
#        │   │   L─ feature/
#        │   │       L─ feature-eelf-1.1.0-SNAPSHOT.jar
#        │   L─ install/
#        │       L─ enable
#        │       L─ disable
#        L─ healthcheck/
#            L── config/
#            │   L─ feature-healthcheck.properties
#            L─ lib/
#                L─ feature/
#                    L─ feature-healthcheck-1.1.0-SNAPSHOT.jar
# #############################################################

if [[ ${DEBUG} == y ]]; then
	echo "-- MAIN --"
	set -x
fi
	
# The directories at play

LIB=${POLICY_HOME}/lib
CONFIG=${POLICY_HOME}/config
DB=${POLICY_HOME}/etc/db/migration
FEATURES=${POLICY_HOME}/features
PROFILED=${POLICY_HOME}/etc/profile.d

if [[ ! ( -d "${LIB}" && -x "${LIB}" ) ]]; then
	echo "error: no ${LIB} directory"
	exit 1
fi

if [[ ! ( -d "${CONFIG}" && -x "${CONFIG}" ) ]]; then
	echo "error: no ${CONFIG} directory"
	exit 2
fi

# ensure that the directory exists
mkdir -p "${FEATURES}" 2> /dev/null

if [[ ! -d "${DB}" ]]; then
	mkdir -p "${DB}"
fi

# relative per Feature Directory Paths

FEATURE_DEPS="lib/dependencies"
FEATURE_LIB="lib/feature"
FEATURE_CONFIG="config"
FEATURE_INSTALL="install"
FEATURE_DB="db"
FEATURE_SQL="sql"

UPGRADE_SQL_SUFFIX=".upgrade.sql"
DOWNGRADE_SQL_SUFFIX=".downgrade.sql"
ENVIRONMENT_SUFFIX=".environment"

featureJars=$(find "${FEATURES}" -name "feature-*.jar" -type f -exec basename {} \; 2> /dev/null)

# default field lengths
nameLength=20
versionLength=15

# update field lengths, if needed
for jar in ${featureJars} ; do
	# get file name without 'jar' suffix
	tmp="${jar%\.jar}"

	# remove feature prefix
	tmp="${tmp#feature-}"
		
	# get feature name by removing the version portion
	name="${tmp%%-[0-9]*}"

	# extract version portion of name
	version="${tmp#${name}-}"

	# grow the size of the name/version field, if needed
	if (( "${#name}" > nameLength )) ; then
		nameLength="${#name}"
	fi
	if (( "${#version}" > versionLength )) ; then
		versionLength="${#version}"
	fi
done

# ##########################################################
# usage: usage information
# ##########################################################
function usage
{
		# print out usage information
		cat >&2 <<-'EOF'
		Usage:  features status
		            Get enabled/disabled status on all features
		        features enable <feature> ...
		            Enable the specified feature
		        features disable <feature> ...
		            Disable the specified feature
		        features install [ <feature> | <file-name> ] ...
		            Install the specified feature
		        features uninstall <feature> ...
		            Uninstall the specified feature
		EOF
}

# ##########################################################
# status: dump out status information
# ##########################################################
function status
{
	if [[ ${DEBUG} == y ]]; then
		echo "-- ${FUNCNAME[0]} --"
		set -x
	fi
	
	local tmp name version status
	local format="%-${nameLength}s %-${versionLength}s %s\n"
	
	printf "${format}" "name" "version" "status"
	printf "${format}" "----" "-------" "------"
	
	for jar in ${featureJars} ; do
		# get file name without 'jar' suffix
		tmp="${jar%\.jar}"
		
		# remove feature prefix
		tmp="${tmp#feature-}"

		# get feature name by removing the version portion
		name="${tmp%%-[0-9]*}"

		# extract version portion of name
		version="${tmp#${name}-}"

		# determine status
		status=disabled
		if [[ -e "${LIB}/${jar}" ]] ; then
			status=enabled
		fi
		printf "${format}" "${name}" "${version}" "${status}"
	done
	echo
}

# ##########################################################
# enableDepAnalysis (featureName):  
#                   reports on potential dependency conflicts
#   featureName: name of the feature
# ##########################################################
function enableDepAnalysis ()
{
	if [[ ${DEBUG} == y ]]; then
		echo "-- ${FUNCNAME[0]} $* --"
		set -x
	fi
	
	local featureName="$1"
	local featureDepJars featureDepJarPath depJarName multiVersionJars
	
	if [[ -z ${featureName} ]]; then
		echo "warning: no feature name"
		return 1
	fi
	
	featureDepJars=$(ls "${FEATURES}"/"${featureName}"/"${FEATURE_DEPS}"/*.jar 2> /dev/null)
	for featureDepJarPath in ${featureDepJars}; do
		depJarName=$(basename "${featureDepJarPath}")
		
		# it could be a base jar

		if [[ -f "${LIB}"/"${depJarName}" ]]; then
			echo "warning: dependency ${depJarName} already in use"
			continue
		fi
		
		# it could be a link from another feature

		if [[ -L "${LIB}"/"${depJarName}" ]]; then
			continue
		fi
		
		# unadvisable if multiple versions exist

		multiVersionJars=$(ls "${LIB}"/"${depJarName%%-[0-9]*.jar}"-*.jar 2> /dev/null)
		if [[ -n "${multiVersionJars}" ]]; then
			echo "error: other version of library ${depJarName} present: ${multiVersionJars}"
			return 2
		fi
	done
}

# ##########################################################
# enableConfigAnalysis (featureName):  
#                   reports on potential dependency conflicts
#   featureName: name of the feature
# ##########################################################
function enableConfigAnalysis ()
{
	if [[ ${DEBUG} == y ]]; then
		echo "-- ${FUNCNAME[0]} $* --"
		set -x
	fi
	
	local featureName="$1"
	local featureConfigs configPath configFileName
	
	if [[ -z ${featureName} ]]; then
		echo "warning: no feature name"
		return 1
	fi
	
	featureConfigs=$(ls "${FEATURES}"/"${featureName}"/"${FEATURE_CONFIG}"/ 2> /dev/null)
	for configPath in ${featureConfigs}; do
		configFileName=$(basename "${configPath}")
		if [[ -e "${CONFIG}"/"${configFileName}" ]]; then
			echo "error: a config file of the same name is already in the base installation: ${configFileName}"
			return 2
		fi
	done
}

# ##########################################################
# enableDbAnalysis (featureName):  
#                   reports on potential db access problems
#   featureName: name of the feature
# ##########################################################
function enableDbAnalysis()
{
	if [[ ${DEBUG} == y ]]; then
		echo "-- ${FUNCNAME[0]} $* --"
		set -x
	fi
	
	local featureName="$1"
	local featureSqls
	
	if [[ -z ${featureName} ]]; then
		echo "warning: no feature name"
		return 1
	fi
	
	featureSqls=$(ls "${FEATURES}"/"${featureName}"/"${FEATURE_DB}"/*/${FEATURE_SQL}/*${UPGRADE_SQL_SUFFIX} 2> /dev/null)
	if [[ -z ${featureSqls} ]]; then
		return 0
	fi
	
	source "${POLICY_HOME}"/etc/profile.d/base.conf
	if [[ -z ${SQL_HOST} ]] || [[ -z ${SQL_USER} ]] || [[ -z ${SQL_PASSWORD} ]]; then
		echo "error: not existing configuration to contact the database"
		return 2
	fi
	
	# check DB set up
	
	source "${POLICY_HOME}"/etc/profile.d/base.conf
	
	if [[ -z ${SQL_HOST} ]] || [[ -z ${SQL_USER} ]] || [[ -z ${SQL_PASSWORD} ]]; then
		echo "error: database credentials do not exist" 
		return 3
	fi
	
	return 0
}

# ##########################################################
# enableFeatureDeps(featureName):  
#                               enables feature dependencies
#   featureName: name of the feature
# ##########################################################
function enableFeatureDeps()
{
	if [[ ${DEBUG} == y ]]; then
		echo "-- ${FUNCNAME[0]} $* --"
		set -x
	fi
	
	local featureName="$1"
	local featureDeps featureDepPath depJarName
	
	if [[ -z ${featureName} ]]; then
		echo "warning: no feature name"
		return 1
	fi
	
	featureDeps=$(ls "${FEATURES}"/"${featureName}"/"${FEATURE_DEPS}"/*.jar 2> /dev/null)
	for featureDepPath in ${featureDeps}; do
		depJarName=$(basename "${featureDepPath}")
		if [[ ! -f "${LIB}"/"${depJarName}" ]]; then
			ln -s -f "${featureDepPath}" "${LIB}/"
		fi
	done
}

# ##########################################################
# enableFeatureConfig(featureName):  
#                               enables feature configuration
#   featureName: name of the feature
# ##########################################################
function enableFeatureConfig()
{
	if [[ ${DEBUG} == y ]]; then
		echo "-- ${FUNCNAME[0]} $* --"
		set -x
	fi
	
	local featureName="$1"
	local featureInstallConf=feature-"${featureName}".conf
	local featureConfigs featureConfigPath
	
	if [[ -z ${featureName} ]]; then
		echo "warning: no feature name"
		return 1
	fi
	
	featureConfigs=$(find "${FEATURES}"/"${featureName}"/"${FEATURE_CONFIG}"/ -type f -maxdepth 1 2> /dev/null)
	for featureConfigPath in ${featureConfigs}; do
		ln -s -f "${featureConfigPath}" "${CONFIG}/"
	done
	
	if [[ -f "${PROFILED}"/"${featureInstallConf}" ]]; then
		ln -s -f "${PROFILED}"/"${featureInstallConf}" "${CONFIG}"/"${featureInstallConf}""${ENVIRONMENT_SUFFIX}"
	fi
}

# ##########################################################
# enableFeatureDbSchema(featureName):  
#        enables feature DB Schema configuration
#   featureName: name of the feature
# ##########################################################
function enableFeatureDbSchema()
{
	if [[ ${DEBUG} == y ]]; then
		echo "-- ${FUNCNAME[0]} $* --"
		set -x
	fi
	
	local featureName="$1"
	local featureDbPath="$2"
	local schemaName="$3"
	
	if [[ -z ${featureName} ]]; then
		echo "warning: no feature name"
		return 1
	fi
	
	if [[ -z ${featureDbPath} ]]; then
		echo "warning: ${featureName} contains no DB path"
		return 2
	fi
	
	if [[ -z ${schemaName} ]]; then
		echo "warning: feature ${featureName} contains no schema name"
		return 3
	fi
	
	rc=0
	sqlUpgradeScripts=$(ls "${featureDbPath%/}"/${FEATURE_SQL}/*${UPGRADE_SQL_SUFFIX} 2> /dev/null)
	for sqlUpgradeScript in ${sqlUpgradeScripts}; do
		if [[ ! -d "${DB}"/"${schemaName}"/${FEATURE_SQL} ]]; then
			mkdir -p "${DB}"/"${schemaName}"/${FEATURE_SQL} 2> /dev/null
		fi		
		ln -s -f "${sqlUpgradeScript}" "${DB}"/"${schemaName}"/${FEATURE_SQL}/
	done
	
	sqlDowngradeScripts=$(ls "${featureDbPath%/}"/${FEATURE_SQL}/*${DOWNGRADE_SQL_SUFFIX} 2> /dev/null)
	for sqlDowngradeScript in ${sqlDowngradeScripts}; do
		if [[ -d "${DB}"/"${schemaName}"/${FEATURE_SQL} ]]; then
			sqlName=$(basename "${sqlDowngradeScript}")
			rm -f "${DB}"/"${schemaName}"/"${FEATURE_SQL}"/"${sqlName}" 2> /dev/null
		else
			echo "warning: feature ${featureName} only contains downgrade scripts"
			rc=4
			break
		fi
	done
	
	if [[ -n ${sqlUpgradeScripts} || -n ${sqlDowngradeScripts} ]]; then
		DEBUG=${DEBUG} db-migrator -s "${schemaName}" -o ok
	fi
	
	return ${rc}
}

# ##########################################################
# enableFeatureDb(featureName):  
#       enables DB feature configuration
#   featureName: name of the feature
# ##########################################################
function enableFeatureDb()
{
	if [[ ${DEBUG} == y ]]; then
		echo "-- ${FUNCNAME[0]} $* --"
		set -x
	fi
	
	local featureName="$1"
	local featureDbs featureDbPath schemaName sqls
	
	if [[ -z ${featureName} ]]; then
		echo "warning: no feature name"
		return 1
	fi
	
	featureDbs=$(ls -d "${FEATURES}"/"${featureName}"/"${FEATURE_DB}"/*/ 2> /dev/null)
	for featureDbPath in ${featureDbs}; do
		sqls=$(ls "${featureDbPath%/}"/"${FEATURE_SQL}"/*.sql 2> /dev/null)
		if [[ -z ${sqls} ]]; then
			continue
		fi
		schemaName=$(basename "${featureDbPath%/}")
		enableFeatureDbSchema "${featureName}" "${featureDbPath%/}" "${schemaName}"
	done
}

# ##########################################################
# customize(featureName): 
#	executes customized script for an operation.
#
# featureName - feature name
# operation - operation, ie. 
#  'enable', 'disable', 'install', or 'uninstall'
# ##########################################################
function customOpScript()
{
	if [[ ${DEBUG} == y ]]; then
		echo "-- ${FUNCNAME[0]} $* --"
		set -x
	fi
	
	local featureName="$1"
	local operation="$2"
	
	if [[ -z ${featureName} ]]; then
		echo "warning: no feature name"
		return 1
	fi
	
	if [[ -z ${operation} ]]; then
		echo "warning: ${featureName} : a custom operation script must be provided"
		return 1
	fi
	
	local customScript="${FEATURES}"/"${featureName}"/"${FEATURE_INSTALL}"/"${operation}"
	if [[ -f ${customScript} ]]; then
		(
			cd "${FEATURES}"/"${featureName}"/"${FEATURE_INSTALL}"
			chmod u+x "${customScript}"
			./"$(basename "${customScript}")"
		)
	fi
}

# ##########################################################
# enableFeature(featureName, featureJar):  enables a feature
#   featureName: name of the feature
#   featureJar:  path to feature jar implementation
# ##########################################################
function enableFeature()
{
	if [[ $DEBUG == y ]]; then
		echo "-- ${FUNCNAME[0]} $* --"
		set -x
	fi
	
	local featureName="$1"
	local featureJar="$2"
	
	if [[ -z ${featureName} ]]; then
		echo "warning: no feature name"
		return 1
	fi
	
	if [[ -z ${featureJar} ]]; then
		echo "warning: no feature jar"
		return 2
	fi
	
	if ! enableDepAnalysis  "${featureName}"; then
		return "$?"
	fi
	
	if ! enableConfigAnalysis  "${featureName}"; then
		return "$?"
	fi
	
	if ! enableDbAnalysis "${featureName}"; then
		return "$?"
	fi
	
	# enable feature itself

	ln -s -f "${featureJar}" "${LIB}/"
		
	# enable dependent libraries if any
	
	enableFeatureDeps "${featureName}"
	
	# enable configuration

	enableFeatureConfig "${featureName}" 
	
	# enable db

	enableFeatureDb "${featureName}"

	# run custom enable if any

	customOpScript "${featureName}" "enable"
}

# ##########################################################
# disableFeatureDeps(featureName):  
#			disables feature dependencies
# ##########################################################
function disableFeatureDeps()
{
	if [[ ${DEBUG} == y ]]; then
		echo "-- ${FUNCNAME[0]} $* --"
		set -x
	fi
	
	local featureName="$1"
	local xDepsEnabledMap featureBaseDirs aFeatureDir aFeatureName
	local featureDeps aFeatureDep 
	local depJarPath depJarName depJarRealPath
	
	if [[ -z ${featureName} ]]; then
		echo "warning: no feature name"
		return 1
	fi
	
	declare -A xDepsEnabledMap
	
	featureBaseDirs=$(ls -d "${FEATURES}"/*/ 2> /dev/null)
	for aFeatureDir in ${featureBaseDirs}; do 
		aFeatureName=$(basename "${aFeatureDir}")
		if [[ "${aFeatureName}" == "${featureName}" ]]; then
			continue
		fi
		
		depJarPaths=$(ls "${aFeatureDir}"/"${FEATURE_DEPS}"/*.jar 2> /dev/null)
		for depJarPath in ${depJarPaths}; do
			if [[ "$?" == 0 ]] ; then
				depJarName=$(basename "${depJarPath}")
				xDepsEnabledMap[${depJarName}]="${depJarPath}"
			fi
		done
	done
	
	if [[ ${DEBUG} == y ]]; then
		echo "${!xDepsEnabledMap[@]}"
		echo "${xDepsEnabledMap[@]}"
	fi
	
	featureDeps=$(ls "${FEATURES}"/"${featureName}"/"${FEATURE_DEPS}"/*.jar 2> /dev/null)
	for aFeatureDep in ${featureDeps}; do
		depJarName=$(basename "${aFeatureDep}")
		if [[ -L "${LIB}"/"${depJarName}" ]]; then
			depJarRealPath=$(readlink -f "${LIB}"/"${depJarName}")
			if [[ "${depJarRealPath}" == "${aFeatureDep}" ]]; then
				rm -f "${LIB}"/"${depJarName}"
				
				# case there were multiple features using this library
				# re-enable link fron an enabled feature
		
				if [[ -n ${xDepsEnabledMap[${depJarName}]} ]]; then
					ln -s -f "${xDepsEnabledMap[${depJarName}]}" "${LIB}/"
				fi
			fi
		fi		
	done
}

# ##########################################################
# disableFeatureConfig(featureName):  
#                               disables feature configuration
#   featureName: name of the feature
# ##########################################################
function disableFeatureConfig()
{
	if [[ ${DEBUG} == y ]]; then
		echo "-- ${FUNCNAME[0]} $* --"
		set -x
	fi
	
	local featureName="$1"
	local featureInstallConf=feature-"${featureName}".conf
	local featureConfigs featureConfigPath
	
	if [[ -z ${featureName} ]]; then
		echo "warning: no feature name"
		return 1
	fi
	
	featureConfigs=$(find "${FEATURES}"/"${featureName}"/"${FEATURE_CONFIG}"/ -type f -maxdepth 1 2> /dev/null)
	for featureConfigPath in ${featureConfigs}; do
		configFileName=$(basename "${featureConfigPath}")
		rm -f "${CONFIG}"/"${configFileName}" 2> /dev/null
	done

	rm -f "${CONFIG}"/"${featureInstallConf}""${ENVIRONMENT_SUFFIX}" 2> /dev/null
}

# ##########################################################
# disableFeatureDbSchema(featureName, featureDbPath, schemaName):  
# 				disables feature db configuration for a schema
#   featureName: name of the feature
# ##########################################################
function disableFeatureDbSchema()
{
	if [[ ${DEBUG} == y ]]; then
		echo "-- ${FUNCNAME[0]} $* --"
		set -x
	fi
	
	local featureName="$1" featureDbPath="$2" schemaName="$3"
	local upgradeFeatureSqls downgradeFeatureSqls featureSql sqlDir sqlName schemaDir schemaName
	
	if [[ -z ${featureName} ]]; then
		echo "warning: no feature name"
		return 1
	fi
	
	if [[ -z ${featureDbPath} ]]; then
		echo "warning: ${featureName} contains no DB path"
		return 2
	fi
	
	if [[ -z ${schemaName} ]]; then
		echo "warning: feature ${featureName} contains no schema name"
		return 3
	fi
	
	
	if [[ -z ${featureName} ]]; then
		echo "warning: no feature name"
		return 1
	fi
	
	upgradeFeatureSqls=$(find "${FEATURES}"/"${featureName}"/"${FEATURE_DB}"/"${schemaName}"/"${FEATURE_SQL}"/*"${UPGRADE_SQL_SUFFIX}" -type f -maxdepth 1 2> /dev/null)
	for featureSql in ${upgradeFeatureSqls}; do
		sqlName=$(basename "${featureSql}")
		sqlDir=$(dirname "${featureSql}")
		schemaDir=$(dirname "${sqlDir}")
		schemaName=$(basename "${schemaDir}")
		rm -f "${DB}"/"${schemaName}"/"${FEATURE_SQL}"/"${sqlName}" 2> /dev/null
	done
	
	downgradeFeatureSqls=$(find "${FEATURES}"/"${featureName}"/"${FEATURE_DB}"/"${schemaName}"/"${FEATURE_SQL}"/*"${DOWNGRADE_SQL_SUFFIX}" -type f -maxdepth 1 2> /dev/null)
	for featureSql in ${downgradeFeatureSqls}; do
		sqlName=$(basename "${featureSql}")
		sqlDir=$(dirname "${featureSql}")
		schemaDir=$(dirname "${sqlDir}")
		schemaName=$(basename "${schemaDir}")
		if [[ ! -d "${DB}"/"${schemaName}"/${FEATURE_SQL} ]]; then
			mkdir -p "${DB}"/"${schemaName}"/${FEATURE_SQL} 2> /dev/null
		fi		
		ln -s -f "${featureSql}" "${DB}"/"${schemaName}"/${FEATURE_SQL}/
	done
	
	if [[ -n ${sqlUpgradeScripts} || -n ${sqlDowngradeScripts} ]]; then
		DEBUG=${DEBUG} db-migrator -s "${schemaName}" -o ok
	fi
}

# ##########################################################
# disableFeatureDb(featureName):  
# 				disables feature db configuration
#   featureName: name of the feature
# ##########################################################
function disableFeatureDb()
{
	if [[ ${DEBUG} == y ]]; then
		echo "-- ${FUNCNAME[0]} $* --"
		set -x
	fi

	local featureName="$1"
	local featureDbPath featureDbs schemaName
	
	if [[ -z ${featureName} ]]; then
		echo "warning: no feature name"
		return 1
	fi
	
	featureDbs=$(ls -d "${FEATURES}"/"${featureName}"/"${FEATURE_DB}"/*/ 2> /dev/null)
	for featureDbPath in ${featureDbs}; do
		if [[ -z "$(ls "${featureDbPath%/}"/"${FEATURE_SQL}"/*${UPGRADE_SQL_SUFFIX} 2> /dev/null)" ]]; then
			continue
		fi
		schemaName=$(basename "${featureDbPath%/}")
		disableFeatureDbSchema "${featureName}" "${featureDbPath%/}" "${schemaName}"
	done
}

# ##########################################################
# disableFeature(featureName):  disables a feature
#   featureName: name of the feature
# ##########################################################
function disableFeature()
{
	if [[ ${DEBUG} == y ]]; then
		echo "-- ${FUNCNAME[0]} $* --"
		set -x
	fi
	
	local featureName="$1"
	
	if [[ -z ${featureName} ]]; then
		echo "warning: no feature name"
		return
	fi
	
	# disable feature itself

	(
	cd "${LIB}"
	rm -f feature-"${featureName}"-[0-9]*.jar 2> /dev/null
	)
		
	# disable dependencies if any

	disableFeatureDeps "${featureName}"
	
	# disable configuration if any

	disableFeatureConfig "${featureName}"
	
	# disable DB SQL scripts if any

	disableFeatureDb "${featureName}"

	# run custom disable if any

	customOpScript "${featureName}" "disable"
}

############################################################
# configureFeature <config-file> <features-root-directory>
#
# This was copied from 'policy-drools/docker-install.sh'
# in the 'docker' repository, and modified where needed.
############################################################
function configureFeature() 
{
	if [[ $DEBUG == y ]]; then
		echo "-- ${FUNCNAME[0]} $* --"
		set -x
	fi

	local envConfig=$1 featureRoot=$2 
	local sedLine="sed -i" 
	local sedFiles="" nonBinaryFiles sedFile name value
		
	while read line || [ -n "${line}" ]; do
		if [[ -n ${line} ]] && [[ ${line:0:1} != \# ]]; then
			name="${line%%=*}"
			value="${line#*=}"
			value=$(echo "${value}" | sed -e 's/[\/&]/\\&/g')
			if [[ -z ${name} ]] || [[ -z ${value} ]]; then
				echo "warning: ${line} missing name or value"
			fi
			sedLine+=" -e 's/\${{${name}}}/${value}/g' "
		fi
	done < "${envConfig}"
	
	nonBinaryFiles=$(find "${featureRoot}" -type f -exec grep -Iq . {} \; -print 2> /dev/null)
	for sedFile in ${nonBinaryFiles}; do
		if fgrep -l '${{' ${sedFile} > /dev/null 2>&1; then
			sedFiles+="${sedFile} "
		fi
	done

	if [[ -n ${sedFiles} ]]; then
		sedLine+=${sedFiles}
		eval "${sedLine}"
	fi
}

############################################################
# installFeatures <feature-name-or-zip-file> ...
#
# This was copied from 'policy-drools/docker-install.sh'
# in the 'docker' repository, and modified where needed.
############################################################
function installFeatures
{
	if [[ $DEBUG == y ]]; then
		echo "-- ${FUNCNAME[0]} $* --"
		set -x
	fi

	local name featureConf feature dir conf
	
	if [[ -d "${FEATURES}" && -x "${FEATURES}" ]]; then
		SOURCE_DIR=$PWD
		for feature in "$@" ; do
			dir=
			if [[ "${feature}" == feature-*.zip ||
						"${feature}" == */feature-*.zip ]] ; then
				# the ZIP file is specified -- find the name
				name="${feature##*/}"
				name="${name#feature-}"
				name="${name%%-[0-9]*\.zip}"

				# if the ZIP file has a directory name component,
				# set 'dir' accordingly
				if [[ "${feature}" =~ / ]] ; then
					dir="${feature%/*}"
				fi
			else
				# Doesn't match the ZIP file name convention -- interpret
				# this as a feature name, and try to locate a matching ZIP
				# file. If there is more than one, choose the one with the
				# highest version number.
				name="${feature}"
				feature=$(ls -v feature-"${name}"-[0-9]*.zip 2>/dev/null|tail -1)
			fi
			if [[ ! -f "${feature}" ]] ; then
				# include the file name in the error message, unless we don't
				# have one -- in this case, use the feature name
				echo "error: feature file ${feature:-for ${name}} not found"
				continue
			fi
			if [[ -d "${FEATURES}/${name}" ]] ; then
				echo "error: feature ${name} has already been installed"
				continue
			fi

			# extract contents of ZIP file in to feature directory
			mkdir -p "${FEATURES}/${name}" > /dev/null 2>&1
			(cd "${FEATURES}/${name}"; jar xf "${SOURCE_DIR}"/"${feature}")

			# if there is a configuration file available,
			# use it to configure the feature
			featureConf="${dir:+$dir/}feature-${name}.conf"
			if [[ -r "${featureConf}" ]]; then
				configureFeature "${featureConf}" "${FEATURES}"/"${name}"
				cp "${featureConf}" "${POLICY_HOME}"/etc/profile.d
				echo "feature ${name} has been installed (configuration present)"
			else
				echo "feature ${name} has been installed (no configuration present)"
			fi
					
			customOpScript "${featureName}" "install"
		done
		
		# check the current directory and the 'config' directory for a
		# 'base.conf' file -- use the first one that is found
		for conf in base.conf ${POLICY_HOME}/config/base.conf ${POLICY_HOME}/etc/profile.d/base.conf; do
			if [[ -f "${conf}" ]] ; then
				echo "applying base configuration '${conf}' to features"
				configureFeature "${conf}" "${FEATURES}"
				break
			fi
		done
	else
		echo "error: aborting -- ${FEATURES} is not accessible"
		exit 1
	fi
}

# ##########################################################
# uninstallFeatureDb(featureName):  
# 				uninstalls the feature db configuration
#   featureName: name of the feature
# ##########################################################
function uninstallFeatureDb()
{
	if [[ ${DEBUG} == y ]]; then
		echo "-- ${FUNCNAME[0]} $* --"
		set -x
	fi

	local featureName="$1"
	local featureSqls sqlDir sqlName schemaDir schemaName schemaNames leftSqls
	
	if [[ -z ${featureName} ]]; then
		echo "warning: no feature name"
		return 1
	fi
	
	featureSqls=$(find "${FEATURES}"/"${featureName}"/"${FEATURE_DB}"/*/${FEATURE_SQL}/*.sql -type f -maxdepth 1 2> /dev/null)
	for featureSql in ${featureSqls}; do
		sqlName=$(basename "${featureSql}")
		sqlDir=$(dirname "${featureSql}")
		schemaDir=$(dirname "${sqlDir}")
		schemaName=$(basename "${schemaDir}")
		schemaNames+=${schemaName} 
		rm -f "${DB}"/"${schemaName}"/"${FEATURE_SQL}"/"${sqlName}" 2> /dev/null
	done
	
	for schemaName in ${schemaNames};
	do
		leftSqls=$(ls "${DB}"/"${schemaName}"/"${FEATURE_SQL}"/*.sql 2> /dev/null)
		if [[ -z ${leftSqls} ]]; then
			if ! DEBUG=${DEBUG} db-migrator -s "${schemaName}" -o ok; then
				echo -n "warning: ${featureName}: ${schemaName}: database data is leftover. "
				echo -n "Consider cleaning left over data with 'db-migrator'."
			fi
		fi		
	done
}

############################################################
# uninstallFeature <feature-name> ...
############################################################
function uninstallFeature
{
	if [[ $DEBUG == y ]]; then
		echo "-- ${FUNCNAME[0]} $* --"
		set -x
	fi
	
	local featureName="$1"
	
	if [[ -z ${featureName} ]]; then
		echo "warning: no feature name"
		return
	fi
	
	disableFeature "${featureName}"
	uninstallFeatureDb "${featureName}"
	customOpScript "${featureName}" "uninstall"
	
	if [[ -n ${FEATURES} && -n ${featureName} ]]; then
		rm -rf "${FEATURES:-???}/${featureName}"
	fi
}

############################################################
# uninstallFeatures <feature-name> ...
############################################################
function uninstallFeatures
{
	if [[ $DEBUG == y ]]; then
		echo "-- ${FUNCNAME[0]} --"
		set -x
	fi
	
	local name
	local allFeatures=$'\n'$(cd ${FEATURES};ls)$'\n'
	for name in "$@" ; do
		# the following check takes care of potentially troublesome names
		# like '.', '..', and names containing '/'
		if [[ "${allFeatures}" =~ $'\n'${name}$'\n' ]] ; then
			uninstallFeature "${name}"
		else
			echo "feature ${name} not found"
		fi
	done
}

case "$1" in
	status)
	{
		# dump out status information
		status
	};;

	enable)
	{
		if [[ -f "${POLICY_HOME}"/PID ]]; then
			echo "error: enable: not allowed when policy is running .."
			echo
			status
			exit 10
		fi
		
		# enable the specified options
		shift
		match=
		for name in "$@" ; do
			# look for matches - 'file' has the full path name
			file=$(ls "${FEATURES}"/"${name}"/"${FEATURE_LIB}"/feature-"${name}"-[0-9]*.jar 2> /dev/null)
			if [[ "$?" != 0 ]] ; then
				# no matching file
				echo "${name}:  no such option"
			else
				# make sure there is only one feature jar
				countFeatureJars=$(echo "${file}" | wc -w)
				if [[ ${countFeatureJars} != 1 ]]; then
					echo "warning: skipping ${name},  ${countFeatureJars} feature libraries found"
					continue
				fi			
				
				# found a match (handle multiple matches, just in case)
				match=true
				
				enableFeature "${name}" "${file}"
			fi
		done
		if [[ "${match}" ]] ; then
			echo
			status
		fi
	};;

	disable)
	{
		if [[ -f "${POLICY_HOME}"/PID ]]; then
			echo "error: disable: not allowed when policy is running .."
			echo
			status
			exit 11
		fi
		
		# disable the specified options
		shift
		match=
		for name in "$@" ; do
			# look for matches -- 'file' has the last segment of the path name
			file=$(ls "${FEATURES}"/"${name}"/"${FEATURE_LIB}"/feature-"${name}"-[0-9]*.jar 2> /dev/null)
			if [[ "$?" != 0 ]] ; then
				echo "${name}:  no such option"
			else
				# found a match (handle multiple matches, just in case)
				match=true
				
				disableFeature "${name}"
			fi
		done
		if [[ "${match}" ]] ; then
			echo
			status
		fi
	};;

	install)
	{
		shift
		installFeatures "$@"
	};;

	uninstall)
	{
		if [[ -f "${POLICY_HOME}"/PID ]]; then
			echo "error: uninstall: not allowed when policy is running .."
			echo
			status
			exit 12
		fi
		shift
		uninstallFeatures "$@"
	};;

	*)
	{
		usage
	};;
esac
exit