All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.openliberty.tools.gradle.tasks.AbstractServerTask.groovy Maven / Gradle / Ivy

The newest version!
/**
 * (C) Copyright IBM Corporation 2017, 2024.
 *
 * 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.
 */
package io.openliberty.tools.gradle.tasks

import groovy.xml.StreamingMarkupBuilder
import groovy.xml.XmlParser
import groovy.xml.XmlNodePrinter
import io.openliberty.tools.ant.ServerTask
import io.openliberty.tools.common.plugins.config.ApplicationXmlDocument
import io.openliberty.tools.common.plugins.config.ServerConfigDocument
import io.openliberty.tools.common.plugins.config.ServerConfigXmlDocument
import io.openliberty.tools.common.plugins.util.DevUtil
import io.openliberty.tools.common.plugins.util.LibertyPropFilesUtility
import io.openliberty.tools.common.plugins.util.PluginExecutionException
import io.openliberty.tools.gradle.utils.CommonLogger
import org.apache.commons.io.FileUtils
import org.apache.commons.io.FilenameUtils
import org.apache.commons.io.filefilter.FileFilterUtils
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ModuleDependency
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.bundling.War
import org.gradle.plugins.ear.Ear

import javax.xml.parsers.ParserConfigurationException
import javax.xml.transform.TransformerException
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.util.Map.Entry
import java.util.regex.Matcher
import java.util.regex.Pattern

abstract class AbstractServerTask extends AbstractLibertyTask {

    protected final String HEADER = "# Generated by liberty-gradle-plugin"

    private static final String LIBERTY_CONFIG_GRADLE_PROPS = "(^liberty\\.server\\.(env|jvmOptions|bootstrapProperties|var|defaultVar|keys))\\.(.+)"
    private static final Pattern pattern = Pattern.compile(LIBERTY_CONFIG_GRADLE_PROPS)

    protected final String PLUGIN_VARIABLE_CONFIG_OVERRIDES_XML = "configDropins/overrides/liberty-plugin-variable-config.xml"
    protected final String PLUGIN_VARIABLE_CONFIG_DEFAULTS_XML = "configDropins/defaults/liberty-plugin-variable-config.xml"

    protected final String CONTAINER_PROPERTY = 'dev_mode_container'

    protected Properties bootstrapProjectProps = new Properties()
    protected Properties envProjectProps = new Properties()
    protected List jvmProjectProps = new ArrayList()
    protected Properties varProjectProps = new Properties()
    protected Properties defaultVarProjectProps = new Properties()

    protected Map combinedBootstrapProperties = null
    protected List combinedJvmOptions = null
    protected Map combinedEnvProperties = null
    protected ServerConfigDocument scd = null;

    protected def server
    protected def springBootBuildTask

    private enum PropertyType {
        BOOTSTRAP("liberty.server.bootstrapProperties"),
        ENV("liberty.server.env"),
        JVM("liberty.server.jvmOptions"),
        VAR("liberty.server.var"),
        DEFAULTVAR("liberty.server.defaultVar");

        private final String name

        private PropertyType(final String propName) {
            this.name = propName
        }

        private static final Map lookup = new HashMap()

        static {
            for (PropertyType s : EnumSet.allOf(PropertyType.class)) {
               lookup.put(s.name, s)
            }
        }

        public static PropertyType getPropertyType(String propertyName) {
            PropertyType pt = lookup.get(propertyName)
            if (pt == null) {
                // get a matcher object from pattern 
                Matcher matcher = pattern.matcher(propertyName)
  
                // check whether Regex string is found in propertyName or not 
                if (matcher.find()) {
                    // strip off the end of the property name to get the prefix
                    String prefix = matcher.group(1);
                    pt = lookup.get(prefix);
                }
            }
            return pt
        } 

        public static String getSuffix(propertyName) {
            // get a matcher object from pattern 
            Matcher matcher = pattern.matcher(propertyName)
 
            // check whether Regex string is found in propertyName or not 
            if (matcher.find()) {
                // strip off the beginning of the property name 
                String suffix = matcher.group(3)
                // strip off surrounding quotation marks
                if (suffix.startsWith("\"") && suffix.endsWith("\"")) {
                    suffix = suffix.substring(1, suffix.length() -1)
                }
                return suffix
            }
            return null
        }

    }

    protected determineSpringBootBuildTask() {
        if (isSpringBoot2plus(springBootVersion)) {
            return project.bootJar
        } else if (isSpringBoot1(springBootVersion)) {
            return project.bootRepackage
        }
    }

    protected void executeServerCommand(Project project, String command, Map params) {
        project.ant.taskdef(name: 'server',
                            classname: 'io.openliberty.tools.ant.ServerTask',
                            classpath: project.buildscript.configurations.classpath.asPath)
        params.put('operation', command)
        project.ant.server(params)
    }

    protected Map buildLibertyMap(Project project) {
        Map result = new HashMap();
        result.put('serverName', server.name)

        def installDir = getInstallDir(project)
        result.put('installDir', installDir)

        def userDir = getUserDir(project, installDir)
        result.put('userDir', userDir)

        result.put('outputDir', getOutputDir(project))

        if (server.timeout != null && !server.timeout.isEmpty()) {
            result.put('timeout', server.timeout)
        }

        return result;
    }

    protected List buildCommand (String operation) {
        List command = new ArrayList()
        String installDir = getInstallDir(project).toString()

        if (isWindows) {
            command.add(installDir + "\\bin\\server.bat")
        } else {
            command.add(installDir + "/bin/server")
        }
        command.add(operation)
        command.add(server.name)

        return command
    }

    protected File getServerDir(Project project){
        return new File(getUserDir(project).toString() + "/servers/" + server.name)
    }

    protected String getOutputDir(Project project) {
        if (server.outputDir != null) {
            return server.outputDir
        } else if (project.liberty.outputDir != null) {
            return project.liberty.outputDir
        } else {
            return getUserDir(project).toString() + "/servers"
        }
    }

    protected void initializeConfigDirectory() {
        if (server.configDirectory == null) {
            server.configDirectory = new File(project.projectDir, "src/main/liberty/config")
        }
    }

    // Use this method to copy over the server.xml file from the defaultServer template.
    // Returns true if the server.xml file does not exist and the defaultServer template server.xml file is copied over, false otherwise.
    protected boolean copyDefaultServerTemplate(File installDir, File serverDir) {
        File serverXmlFile = new File(serverDir, "server.xml")
        if (!serverXmlFile.exists()) {
            File defaultServerTemplate = new File(installDir, "templates/servers/defaultServer/server.xml")
            if (defaultServerTemplate.exists()) {
                Files.copy(defaultServerTemplate.toPath(), serverXmlFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
                return true;
            }
        }
        return false;
    }
    
    protected void copyConfigDirectory() {
        //merge default server.env with one in config directory
        File configDirServerEnv = new File(server.configDirectory, "server.env")
        if (configDirServerEnv.exists() && server.mergeServerEnv) {
            FileFilter fileFilter =   FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter("server.env", null))
            FileUtils.copyDirectory(server.configDirectory, getServerDir(project), fileFilter)

            Properties configDirServerEnvProps = convertServerEnvToProperties(configDirServerEnv)

            File defaultEnvFile = new File(getServerDir(project), "server.env")
            Properties defaultServerEnvProps = convertServerEnvToProperties(defaultEnvFile)

            Properties mergedProperties = combineServerEnvProperties(defaultServerEnvProps, configDirServerEnvProps)
            writeServerEnvProperties(defaultEnvFile, mergedProperties)

        }
        else {
            // replace entire directory with configured configDirectory 
            FileUtils.copyDirectory(server.configDirectory, getServerDir(project))
        }
    }

    /**
     * @throws IOException
     * @throws FileNotFoundException
     */
    protected void copyConfigFiles() throws IOException {

        String serverDirectory = getServerDir(project).toString()
        String serverXMLPath = null
        String jvmOptionsPath = null
        String bootStrapPropertiesPath = null
        String serverEnvPath = null

        // First check for Liberty configuration specified by Gradle project properties.
        loadLibertyConfigFromProperties();
      
        // make sure server.configDirectory exists
        initializeConfigDirectory();

        if(server.configDirectory.exists()) {
            
            // copy configuration files from configuration directory to server directory if end-user set it
            copyConfigDirectory()

            File configDirServerXML = new File(server.configDirectory, "server.xml")
            if (configDirServerXML.exists()) {
                serverXMLPath = configDirServerXML.getCanonicalPath()
            }

            File configDirJvmOptionsFile = new File(server.configDirectory, "jvm.options")
            if (configDirJvmOptionsFile.exists()) {
                jvmOptionsPath = configDirJvmOptionsFile.getCanonicalPath()
            }

            File configDirBootstrapFile = new File(server.configDirectory, "bootstrap.properties")
            if (configDirBootstrapFile.exists()) {
                bootStrapPropertiesPath = configDirBootstrapFile.getCanonicalPath()
            }

            File configDirServerEnv = new File(server.configDirectory, "server.env")
            if (configDirServerEnv.exists()) {
                serverEnvPath = configDirServerEnv.getCanonicalPath()
            }
        }

        // serverXmlFile takes precedence over server.xml from configDirectory
        // copy configuration file to server directory if end-user set it.
        if (server.serverXmlFile != null && server.serverXmlFile.exists()) {
            Files.copy(server.serverXmlFile.toPath(), new File(serverDirectory, "server.xml").toPath(), StandardCopyOption.REPLACE_EXISTING)
            serverXMLPath = server.serverXmlFile.getCanonicalPath()
        }

        // jvmOptions, jvmOptionsFile and jvmProjectProps take precedence over jvm.options from configDirectory
        File optionsFile = new File(serverDirectory, "jvm.options")
        if (optionsFile.exists() && jvmOptionsPath == null) {
            // if using pre-existing installation, do not delete file
            if (project.liberty.installDir == null) {
                logger.info(optionsFile.getCanonicalPath() + " file deleted before processing plugin configuration.")
                optionsFile.delete();
            }
        }
        if((server.jvmOptions != null && !server.jvmOptions.isEmpty()) || !jvmProjectProps.isEmpty()){
            if (jvmOptionsPath != null) {
                logger.info("The " + jvmOptionsPath + " file is overwritten by inlined configuration.")
            }
            writeJvmOptions(optionsFile, server.jvmOptions, jvmProjectProps)
            jvmOptionsPath = "inlined configuration"
        } else if (server.jvmOptionsFile != null && server.jvmOptionsFile.exists()) {
            if (jvmOptionsPath != null) {
                logger.info("The " + jvmOptionsPath + " file is overwritten by the " + server.jvmOptionsFile.getCanonicalPath() + " file.");
            }
            Files.copy(server.jvmOptionsFile.toPath(), optionsFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
            jvmOptionsPath = server.jvmOptionsFile.getCanonicalPath()
        }

        // bootstrapProperties, bootstrapPropertiesFile and bootstrapProjectProps take precedence over 
        // bootstrap.properties from configDirectory
        File bootstrapFile = new File(serverDirectory, "bootstrap.properties")
        if (bootstrapFile.exists() && bootStrapPropertiesPath == null) {
            // if using pre-existing installation, do not delete file
            if (project.liberty.installDir == null) {
                logger.info(bootstrapFile.getCanonicalPath() + " file deleted before processing plugin configuration.")
                bootstrapFile.delete();
            }
        }
        if((server.bootstrapProperties != null && !server.bootstrapProperties.isEmpty()) || !bootstrapProjectProps.isEmpty()){
            if (bootStrapPropertiesPath != null) {
                logger.info("The " + bootStrapPropertiesPath + " file is overwritten by inlined configuration.")
            }
            writeBootstrapProperties(bootstrapFile, server.bootstrapProperties, bootstrapProjectProps)
            bootStrapPropertiesPath = "inlined configuration"
        } else if (server.bootstrapPropertiesFile != null && server.bootstrapPropertiesFile.exists()) {
            if (bootStrapPropertiesPath != null) {
                logger.info("The " + bootStrapPropertiesPath + " file is overwritten by the " + server.bootstrapPropertiesFile.getCanonicalPath() + " file.")
            }
            Files.copy(server.bootstrapPropertiesFile.toPath(), bootstrapFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
            bootStrapPropertiesPath = server.bootstrapPropertiesFile.getCanonicalPath()
        }

        // envProjectProps and serverEnvFile take precedence over server.env from configDirectory
        serverEnvPath = handleServerEnvFileAndProperties(serverEnvPath, serverDirectory)

        // generate a config file on the server with any Liberty configuration variables specified via project properties
        File pluginVariableConfig = new File(serverDirectory, PLUGIN_VARIABLE_CONFIG_OVERRIDES_XML)
        if (pluginVariableConfig.exists()) {
            logger.debug(pluginVariableConfig.getCanonicalPath() + " file deleted before processing plugin configuration.")
            pluginVariableConfig.delete();
        }
        if ((server.var != null && !server.var.isEmpty()) || !varProjectProps.isEmpty()) {
            writeConfigDropinsServerVariables(pluginVariableConfig, server.var, varProjectProps, false)
            logger.info("Generate server configuration file " + pluginVariableConfig.getCanonicalPath())
        }

        // generate a config file on the server with any Liberty configuration default variables specified via project properties
        pluginVariableConfig = new File(serverDirectory, PLUGIN_VARIABLE_CONFIG_DEFAULTS_XML)
        if (pluginVariableConfig.exists()) {
            logger.debug(pluginVariableConfig.getCanonicalPath() + " file deleted before processing plugin configuration.")
            pluginVariableConfig.delete();
        }
        if ((server.defaultVar != null && !server.defaultVar.isEmpty()) || !defaultVarProjectProps.isEmpty()) {
            writeConfigDropinsServerVariables(pluginVariableConfig, server.defaultVar, defaultVarProjectProps, true)
            logger.info("Generate server configuration file " + pluginVariableConfig.getCanonicalPath())
        }

        // log info on the configuration files that get used
        if (serverXMLPath != null && !serverXMLPath.isEmpty()) {
            logger.info("Update server configuration file server.xml from " + serverXMLPath)
        }
        if (jvmOptionsPath != null && !jvmOptionsPath.isEmpty()) {
            logger.info("Update server configuration file jvm.options from " + jvmOptionsPath)
        }
        if (bootStrapPropertiesPath != null && !bootStrapPropertiesPath.isEmpty()) {
            logger.info("Update server configuration file bootstrap.properties from " + bootStrapPropertiesPath)
        }
        if (serverEnvPath != null && !serverEnvPath.isEmpty()) {
            logger.info("Update server configuration file server.env from " + serverEnvPath)
        }

        writeServerPropertiesToXml(project)
    }

    private void loadLibertyConfigFromProperties() {
        Set> entries = project.getProperties().entrySet()
        for (Entry entry : entries) {
            String key = (String) entry.getKey()
            PropertyType propType = PropertyType.getPropertyType(key)

            if (propType != null) {
                String suffix = PropertyType.getSuffix(key)
                if (suffix != null) {
                    // dealing with single property
                    Object value = entry.getValue()
                    String propValue = value == null ? null : value.toString()
                    if (propValue != null && propValue.startsWith("\"") && propValue.endsWith("\"")) {
                        propValue = propValue.substring(1, propValue.length() -1)
                    }

                    addProjectProperty(suffix, propValue, propType)
                } else {
                    // dealing with array of properties
                    Object value = entry.getValue()
                    String propValue = value == null ? null : value.toString()
                    if ( (propValue != null) && ( (propValue.startsWith("{") && propValue.endsWith("}")) || (propValue.startsWith("[") && propValue.endsWith("]")) ) ) {
                        propValue = propValue.substring(1, propValue.length() -1)
                    }

                    // parse the array where properties are delimited by commas and the name/value are separated with a colon
                    String[] values = propValue.split(",")
                    for (String nextNameValuePair : values) {
                        String trimmedNameValuePair = nextNameValuePair.trim()
                        String[] splitNameValue = trimmedNameValuePair.split(":")
                        String nextPropName = splitNameValue[0].trim()

                        // remove surrounding quotes from property names and property values
                        if (nextPropName.startsWith("\"") && nextPropName.endsWith("\"")) {
                            nextPropName = nextPropName.substring(1, nextPropName.length() -1)
                        }

                        String nextPropValue = null
                        if (splitNameValue.length == 2) {
                            nextPropValue = splitNameValue[1].trim()
                            if (nextPropValue.startsWith("\"") && nextPropValue.endsWith("\"")) {
                                nextPropValue = nextPropValue.substring(1, nextPropValue.length() -1)
                            }
                        }

                        addProjectProperty(nextPropName, nextPropValue, propType)
                    }
                }
            }
        }
    }

    private void addProjectProperty(String propName, String propValue, PropertyType propType) {
        if (propValue != null) {
            logger.debug("Processing Liberty configuration from property with type "+ propType +" and name "+ propName +" and value "+ propValue)
        } else {
            logger.debug("Processing Liberty configuration from property with type "+ propType +" and value " + propName)
        }
        switch (propType) {
            case PropertyType.ENV:        envProjectProps.setProperty(propName, propValue)
                                          break
            case PropertyType.BOOTSTRAP:  bootstrapProjectProps.setProperty(propName, propValue)
                                          break
            case PropertyType.JVM:        jvmProjectProps.remove(propName)  // avoid exact duplicates
                                          jvmProjectProps.add(propName)
                                          break
            case PropertyType.VAR:        varProjectProps.setProperty(propName, propValue)
                                          break
            case PropertyType.DEFAULTVAR: defaultVarProjectProps.setProperty(propName, propValue)
                                          break
        }
    }

    protected void setServerDirectoryNodes(Project project, Node serverNode) {
        serverNode.appendNode('userDirectory', getUserDir(project).toString())
        serverNode.appendNode('serverDirectory', getServerDir(project).toString())
        serverNode.appendNode('serverOutputDirectory', new File(getOutputDir(project), server.name))
    }

    protected void setServerPropertyNodes(Project project, Node serverNode) {
        serverNode.appendNode('serverName', server.name)
        if (server.configDirectory != null && server.configDirectory.exists()) {
            serverNode.appendNode('configDirectory', server.configDirectory.toString())
        }

        if (server.serverXmlFile != null && server.serverXmlFile.exists()) {
            serverNode.appendNode('configFile', server.serverXmlFile.toString())
        }

        if (combinedBootstrapProperties != null) {
            Node bootstrapProperties = new Node(null, 'bootstrapProperties')
            combinedBootstrapProperties.each { k, v ->
                bootstrapProperties.appendNode(k, v.toString())
            }
            serverNode.append(bootstrapProperties)
        } else if (server.bootstrapProperties != null && !server.bootstrapProperties.isEmpty()) {
            Node bootstrapProperties = new Node(null, 'bootstrapProperties')
            server.bootstrapProperties.each { k, v ->
                bootstrapProperties.appendNode(k, v.toString())
            }
            serverNode.append(bootstrapProperties)
        } else if (server.bootstrapPropertiesFile != null && server.bootstrapPropertiesFile.exists()) {
            serverNode.appendNode('bootstrapPropertiesFile', server.bootstrapPropertiesFile.toString())
        }

        if (combinedJvmOptions != null) {
            Node jvmOptions = new Node(null, 'jvmOptions')
            combinedJvmOptions.each { v ->
                jvmOptions.appendNode('params', v.toString())
            }
            serverNode.append(jvmOptions)
        } else if (server.jvmOptions != null && !server.jvmOptions.isEmpty()) {
            Node jvmOptions = new Node(null, 'jvmOptions')
            server.jvmOptions.each { v ->
                jvmOptions.appendNode('params', v.toString())
            }
            serverNode.append(jvmOptions)
        } else if (server.jvmOptionsFile != null && server.jvmOptionsFile.exists()) {
            serverNode.appendNode('jvmOptionsFile', server.jvmOptionsFile.toString())
        }

        // Only write the serverEnvFile path if it was not overridden by liberty.env.{var} project properties.
        if (envProjectProps.isEmpty() && server.serverEnvFile != null && server.serverEnvFile.exists()) {
            serverNode.appendNode('serverEnv', server.serverEnvFile.toString())
        }

        serverNode.appendNode('looseApplication', server.looseApplication)
        serverNode.appendNode('stripVersion', server.stripVersion)

        configureMultipleAppsConfigDropins(serverNode)
    }

    protected ServerConfigDocument getServerConfigDocument(CommonLogger log, File serverXML) throws IOException {

        if (scd == null || !scd.getOriginalServerXMLFile().getCanonicalPath().equals(serverXML.getCanonicalPath())) {
            try {
                scd = new ServerConfigDocument(log, serverXML, getInstallDir(project), getUserDir(project), getServerDir(project));
            } catch (PluginExecutionException e) {
                throw new GradleException(e.getMessage());
            }
        }

        return scd
    }

    protected boolean isAppConfiguredInSourceServerXml(String fileName) {
        boolean configured = false;
        File serverConfigFile = new File(getServerDir(project), 'server.xml')
        if (serverConfigFile != null && serverConfigFile.exists()) {
            try {
                Map props = combinedBootstrapProperties == null ? convertPropertiesToMap(server.bootstrapProperties) : combinedBootstrapProperties;
                getServerConfigDocument(new CommonLogger(project), serverConfigFile);
                if (scd != null && isLocationFound( scd.getLocations(), fileName)) {
                    logger.debug("Application configuration is found in server.xml : " + fileName)
                    configured = true
                } else {
                    logger.debug("Application configuration is not found in server.xml : " + fileName)
                }
            }
            catch (Exception e) {
                logger.warn(e.getLocalizedMessage())
            }
        }
        return configured
    }

    protected boolean isLocationFound(Set locations, String fileName) {
        if (locations == null) {
            return false
        }
        
        if (locations.contains(fileName)) {
            return true
        }

        for (String nextLocation : locations) {
            int index = nextLocation.lastIndexOf("/")
            if (index > -1) {
                String appName = nextLocation.substring(index+1)
                if (fileName.equals(appName)) {
                    return true
                }
            }
        }

        return false
    }

    // Gradle passes the properties from the configuration as Strings and Integers and maybe Booleans.
    // Need to convert to the String values for those Objects before passing along to ServerConfigDocument.
    protected Map convertPropertiesToMap(Properties props) {
        if (props == null) {
            return null
        }

        Map returnProps = new HashMap ()

        Set> entries = props.entrySet()
        for (Entry entry : entries) {
            String key = (String) entry.getKey()
            Object value = entry.getValue()
            if (value != null) {
                returnProps.put(key,value.toString())
            }
        }
        return returnProps
    }
    
    protected String getArchiveName(Task task){
        if (isSpringBoot1(springBootVersion)) {
            task = project.jar
        }
        if (server.stripVersion){
            return task.getArchiveBaseName().get() + "." + task.getArchiveExtension().get()
        }
        return task.getArchiveFileName().get();
    }

    protected void configureApps(Project project) {
        if ((server.deploy.apps == null || server.deploy.apps.isEmpty()) && (server.deploy.dropins == null || server.deploy.dropins.isEmpty())) {
            if (!project.configurations.libertyApp.isEmpty()) {
                server.deploy.apps = getApplicationFilesFromConfiguration().toArray()
            } else if (project.plugins.hasPlugin('war')) {
                server.deploy.apps = [project.war]
            } else if (project.plugins.hasPlugin('ear')) {
                server.deploy.apps = [project.ear]
            } else if (project.plugins.hasPlugin('org.springframework.boot')) {
                server.deploy.apps = [springBootBuildTask]
            }
        }
    }
    
    protected void configureMultipleAppsConfigDropins(Node serverNode) {
        if (server.deploy.apps != null && !server.deploy.apps.isEmpty()) {
            Tuple applications = splitAppList(server.deploy.apps)
            applications[0].each{ Task task ->
              isConfigDropinsRequired(task, 'apps', serverNode)
            }
        }
    }
    
    protected Tuple splitAppList(List allApps) {
        List appFiles = new ArrayList()
        List appTasks = new ArrayList()

        allApps.each { Object appObj ->
            if (appObj instanceof Task) {
                appTasks.add((Task)appObj)
            } else if (appObj instanceof File) {
                appFiles.add((File)appObj)
            } else {
                logger.warn('Application ' + appObj.getClass.name + ' is expressed as ' + appObj.toString() + ' which is not a supported input type. Define applications using Task or File objects.')
            }
        }

        return new Tuple(appTasks, appFiles)
    }
    
    private boolean isSupportedType(){
      switch (getPackagingType()) {
        case "ear":
        case "war":
            return true;
        default:
            return false;
        }
    }
    protected String getLooseConfigFileName(Task task){
      return getArchiveName(task) + ".xml"
    }
    
    protected void isConfigDropinsRequired(Task task, String appsDir, Node serverNode) {
        File installAppsConfigDropinsFile = ApplicationXmlDocument.getApplicationXmlFile(getServerDir(project))
        if (isSupportedType()) {
          if (server.looseApplication){
            String looseConfigFileName = getLooseConfigFileName(task)
            String application = looseConfigFileName.substring(0, looseConfigFileName.length()-4)
            if (!isAppConfiguredInSourceServerXml(application)) {
                serverNode.appendNode('installAppsConfigDropins', installAppsConfigDropinsFile.toString())
            }
          } else {
                if (!isAppConfiguredInSourceServerXml(getArchiveName(task)) || hasConfiguredApp(ApplicationXmlDocument.getApplicationXmlFile(getServerDir(project)))) {
                    serverNode.appendNode('installAppsConfigDropins', installAppsConfigDropinsFile.toString())
                }
            }
        }
    }

    protected void createApplicationElements(Node applicationsNode, List appList, String appDir) {
        springBootVersion=findSpringBootVersion(project)
        appList.each { Object appObj ->
            Node application = new Node(null, 'application')
            if (appObj instanceof Task) {
                if (isSpringBoot1(springBootVersion)) {
                    appObj = project.jar
                }
                application.appendNode('appsDirectory', appDir)
                if (server.looseApplication) {
                    application.appendNode('applicationFilename', appObj.getArchiveFileName().get() + '.xml')
                } else {
                    application.appendNode('applicationFilename', appObj.getArchiveFileName().get())
                }
                if (appObj instanceof War) {
                    application.appendNode('warSourceDirectory', project.webAppDirName)
                }
            } else if (appObj instanceof File) {
                application.appendNode('appsDirectory', appDir)
                if (server.looseApplication) {
                    application.appendNode('applicationFilename', appObj.name + '.xml')
                } else {
                    application.appendNode('applicationFilename', appObj.name)
                }
            }

            if(!application.children().isEmpty()) {
                if (project.plugins.hasPlugin("war")) {
                    application.appendNode('projectType', 'war')
                } else if (project.plugins.hasPlugin("ear")) {
                    application.appendNode('projectType', 'ear')
                }
                applicationsNode.append(application)
            }
        }
    }

    protected void setApplicationPropertyNodes(Project project, Node serverNode) {
        Node applicationsNode;
        if ((server.deploy.apps == null || server.deploy.apps.isEmpty()) && (server.deploy.dropins == null || server.deploy.dropins.isEmpty())) {
            if (project.plugins.hasPlugin('war')) {
                applicationsNode = new Node(null, 'applications')
                createApplicationElements(applicationsNode, [project.tasks.war], 'apps')
                serverNode.append(applicationsNode)
            }
        } else {
            applicationsNode = new Node(null, 'applications')
            if (server.deploy.apps != null && !server.deploy.apps.isEmpty()) {
                createApplicationElements(applicationsNode, server.deploy.apps, 'apps')
            }
            if (server.deploy.dropins != null && !server.deploy.dropins.isEmpty()) {
                createApplicationElements(applicationsNode, server.deploy.dropins, 'dropins')
            }
            serverNode.append(applicationsNode)
        }
    }

    protected void setDependencyNodes(Project project, Node serverNode) {
        Project parent = project.getParent()
        if (parent != null) {
            serverNode.appendNode('aggregatorParentId', parent.getName())
            serverNode.appendNode('aggregatorParentBasedir', parent.getProjectDir())
        }

        if (project.configurations.findByName('compile') && !project.configurations.compile.dependencies.isEmpty()) {
            project.configurations.compile.dependencies.each { dependency ->
                serverNode.appendNode('projectCompileDependency', dependency.group + ':' + dependency.name + ':' + dependency.version)
            }
        }
    }

    protected void writeServerPropertiesToXml(Project project) {
        XmlParser pluginXmlParser = new XmlParser()
        Node libertyPluginConfig = pluginXmlParser.parse(new File(project.getLayout().getBuildDirectory().getAsFile().get(), 'liberty-plugin-config.xml'))
        if (libertyPluginConfig.getAt('servers').isEmpty()) {
            libertyPluginConfig.appendNode('servers')
        } else {
            //removes the server nodes from the servers element
            libertyPluginConfig.getAt('servers')[0].value = ""
        }
        Node serverNode = new Node(null, 'server')

        setServerDirectoryNodes(project, serverNode)
        setServerPropertyNodes(project, serverNode)
        setApplicationPropertyNodes(project, serverNode)
        setDependencyNodes(project, serverNode)

        libertyPluginConfig.getAt('servers')[0].append(serverNode)

        new File( project.getLayout().getBuildDirectory().getAsFile().get(), 'liberty-plugin-config.xml' ).withWriter('UTF-8') { output ->
            output << new StreamingMarkupBuilder().bind { mkp.xmlDeclaration(encoding: 'UTF-8', version: '1.0' ) }
            XmlNodePrinter printer = new XmlNodePrinter( new PrintWriter(output) )
            printer.preserveWhitespace = true
            printer.print( libertyPluginConfig )
        }

        logger.info ("Adding Liberty plugin config info to ${project.getLayout().getBuildDirectory().getAsFile().get()}/liberty-plugin-config.xml.")
    }

    private void writeBootstrapProperties(File file, Properties properties, Map projectProperties) throws IOException {
        Map convertedProps = convertPropertiesToMap(properties)
        if (! projectProperties.isEmpty()) {
            if (properties == null) {
                combinedBootstrapProperties = projectProperties;
            } else {
                combinedBootstrapProperties = new HashMap ()
                // add the project properties (which come from the command line) last so that they take precedence over the properties specified in build.gradle
                combinedBootstrapProperties.putAll(convertedProps)
                combinedBootstrapProperties.putAll(projectProperties)
            }
        } else {
            combinedBootstrapProperties = convertedProps
        }

        makeParentDirectory(file)
        PrintWriter writer = null
        try {
            writer = new PrintWriter(file, "UTF-8")
            writer.println(HEADER)
            for (Map.Entry entry : combinedBootstrapProperties.entrySet()) {
                writer.print(entry.getKey())
                writer.print("=")
                writer.println((entry.getValue() != null) ? entry.getValue().toString().replace("\\", "/") : "")
            }
        } finally {
            if (writer != null) {
                writer.close()
            }
        }
    }

    // Remove any duplicate entries in the passed in List
    protected List getUniqueValues(List values) {
        List uniqueValues = new ArrayList ();
        if (values == null) {
            return uniqueValues
        }

        for (String nextValue : values) {
            // by removing a matching existing value, it ensures there will not be a duplicate and that this current one will appear later in the List
            if (uniqueValues.contains(nextValue)) {
                getLog().debug("Remove duplicate value: "+nextValue+" at position: "+uniqueValues.indexOf(nextValue))
            }
            uniqueValues.remove(nextValue) // has no effect if the value is not present
            uniqueValues.add(nextValue)
        }
        return uniqueValues
    }

    private void writeJvmOptions(File file, List options, List projectProperties) throws IOException {
        List uniqueOptions = getUniqueValues(options)
        List uniqueProps = getUniqueValues(projectProperties)

        if (! uniqueProps.isEmpty()) {
            if (uniqueOptions.isEmpty()) {
                combinedJvmOptions = uniqueProps;
            } else {
                combinedJvmOptions = new ArrayList ()
                // add the project properties (which come from the command line) last so that they take precedence over the options specified in build.gradle
                combinedJvmOptions.addAll(uniqueOptions)
                combinedJvmOptions.removeAll(uniqueProps) // remove any exact duplicates before adding all the project properties
                combinedJvmOptions.addAll(uniqueProps)
            }
        } else {
            combinedJvmOptions = uniqueOptions
        }

        makeParentDirectory(file)
        PrintWriter writer = null
        try {
            writer = new PrintWriter(file, "UTF-8")
            writer.println(HEADER)
            for (String option : combinedJvmOptions) {
                writer.println(option)
            }
        } finally {
            if (writer != null) {
                writer.close()
            }
        }
    }

    private String handleServerEnvFileAndProperties(String serverEnvPath, String serverDirectory) {
        File envFile = new File(serverDirectory, "server.env")
        Properties configuredProps = combineServerEnvProperties(server.env, envProjectProps);

        if(server.mergeServerEnv) {
            return setServerEnvWithAppendServerEnvHelper(envFile, serverEnvPath, configuredProps)
        }
        else {
            return setServerEnvHelper(envFile, serverEnvPath, configuredProps)
        }
    }

    private String setServerEnvWithAppendServerEnvHelper(File envFile, String serverEnvPath, Properties configuredProps) {
        Properties serverEnvProps = convertServerEnvToProperties(envFile);
        Properties mergedProperties = new Properties();

        if (server.serverEnvFile != null && server.serverEnvFile.exists()) {
            if (serverEnvPath != null) {
                logger.debug("The serverEnvFile "+ server.serverEnvFile.getCanonicalPath() + " is merged with the " + serverEnvPath + " file.")
            }
            else {
                logger.debug("The serverEnvFile "+ server.serverEnvFile.getCanonicalPath() + " is merged with the " + getServerDir(project).getCanonicalPath() + " file.")
            }
            Properties configuredServerEnvProps = convertServerEnvToProperties(server.serverEnvFile);
            //Merge with either default server.env or with what has already been merged if
            mergedProperties = (Properties) combineServerEnvProperties(serverEnvProps, configuredServerEnvProps);
        }

        if (!configuredProps.isEmpty()) {
            if (serverEnvPath != null) {
                logger.debug("The " + serverEnvPath + " file is merged with inlined configuration.")
            }
            else {
                logger.debug("The " + getServerDir(project).getCanonicalPath() + " file is merged with inlined configuration.")
            }

            if (mergedProperties.isEmpty()) {
                mergedProperties = combineServerEnvProperties(serverEnvProps, configuredProps);
            } else {
                mergedProperties = combineServerEnvProperties(mergedProperties, configuredProps);
            }
        }

        if(!mergedProperties.isEmpty()) {
            writeServerEnvProperties(envFile, mergedProperties);
            return setServerEnvPathHelperForAppendServerEnv(envFile, configuredProps, serverEnvPath)
        }

        return serverEnvPath;
    }

    private String setServerEnvPathHelperForAppendServerEnv(File envFile, Properties configuredProps, String serverEnvPath) {
        boolean configDirEnvMerged = serverEnvPath != null;
        boolean serverEnvFileMerged = server.serverEnvFile != null && server.serverEnvFile.exists()
        boolean inlineEnvPropsMerged = !configuredProps.isEmpty()

        StringBuilder updatedServerEnvPath = new StringBuilder("merging");

        if(configDirEnvMerged) {
            updatedServerEnvPath.append(" configDir server.env " +  serverEnvPath + ", ")
        }
        if (serverEnvFileMerged) {
            updatedServerEnvPath.append(" serverEnvFile " +  server.serverEnvFile.getCanonicalPath() + ", ")
        }
        if (inlineEnvPropsMerged) {
            updatedServerEnvPath.append(" env properties, ")
        }
        // remove excess comma and space
        int lastCommaIndex = updatedServerEnvPath.lastIndexOf(", ")
        updatedServerEnvPath = updatedServerEnvPath.replace(lastCommaIndex, lastCommaIndex + 2, ".")
        
        //replace last comma and space with and
        lastCommaIndex = updatedServerEnvPath.lastIndexOf(", ")
        if(lastCommaIndex > 0) {
            updatedServerEnvPath = updatedServerEnvPath.replace(lastCommaIndex, lastCommaIndex + 2, "")
            updatedServerEnvPath = updatedServerEnvPath.insert(lastCommaIndex, " and")
        }

        return updatedServerEnvPath.toString();
    }

    private String setServerEnvHelper(File envFile, String serverEnvPath, Properties configuredProps) {
        if ((server.env != null && !server.env.isEmpty()) || !envProjectProps.isEmpty()) {
            Properties envPropsToWrite = configuredProps
            if (serverEnvPath == null && server.serverEnvFile == null) {
                // Do a special case merge but ONLY if there is no server.env file present in configDirectory or specified with serverEnvFile
                envPropsToWrite = mergeSpecialPropsFromInstallServerEnvIfAbsent(envFile, configuredProps)
                logger.warn("The default " + envFile.getCanonicalPath() + " file is overwritten by inlined configuration.")
            } else if (serverEnvPath != null) {
                logger.warn("The " + serverEnvPath + " file is overwritten by inlined configuration.")
            }
            writeServerEnvProperties(envFile, envPropsToWrite)
            return "inlined configuration"
        } else if (server.serverEnvFile != null && server.serverEnvFile.exists()) {
            if (serverEnvPath != null) {
                logger.warn("The " + serverEnvPath + " file is overwritten by the " + server.serverEnvFile.getCanonicalPath() + " file.")
            }
            Files.copy(server.serverEnvFile.toPath(), envFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
            return server.serverEnvFile.getCanonicalPath()
        }
    }

    /**
     * Merges envProps with special properties found in envFile, the install (target) server.env.  We return a clone/copy of
     * envProps, to which any of a list of special properties found in envFile have been added.  We give precedence
     * to properties already in envProps.
     */
    private Properties mergeSpecialPropsFromInstallServerEnvIfAbsent(File envFile, Properties envProps) throws IOException {

        // Make a copy to avoid side effects 
        Properties mergedProps = new Properties()
        mergedProps.putAll(envProps)

        // From install (target) dir
        Properties serverEnvProps = convertServerEnvToProperties(envFile)

        String propertyName = "keystore_password"
        if (serverEnvProps.containsKey(propertyName)) {
            mergedProps.putIfAbsent(propertyName,serverEnvProps.get(propertyName))
        }

        return mergedProps
    }


    private Properties convertServerEnvToProperties(File serverEnv) {
        Properties serverEnvProps = new Properties();

        if ((serverEnv == null) || !serverEnv.exists()) {
            return serverEnvProps;
        }

        BufferedReader bf = new BufferedReader(new FileReader(serverEnv));
        String line;
        while((line = bf.readLine()) != null) {
            
            //Skip comments
            if(!line.startsWith("#")) {
                String[] keyValue = line.split("=", 2);
                if (keyValue.length == 2) {
                    String key = keyValue[0];
                    String value = keyValue[1];

                    serverEnvProps.put(key,value);
                }
            }
        }

        return serverEnvProps;
    }

    private Properties combineServerEnvProperties(Properties properties, Properties projectProperties) {
        Properties combinedEnvProperties = new Properties()
        if (! projectProperties.isEmpty()) {
            if (properties.isEmpty()) {
                combinedEnvProperties.putAll(projectProperties)
            } else {
                // add the project properties (which come from the command line) last so that they take precedence over the properties specified in build.gradle
                combinedEnvProperties.putAll(properties)
                combinedEnvProperties.putAll(projectProperties)
            }
        } else {
            combinedEnvProperties.putAll(properties)
        }

        return combinedEnvProperties;
    }
    
    private void writeServerEnvProperties(File file, Properties combinedEnvProperties) throws IOException {
        makeParentDirectory(file)
        PrintWriter writer = null
        try {
            writer = new PrintWriter(file, "UTF-8")
            writer.println(HEADER)
            for (Map.Entry entry : combinedEnvProperties.entrySet()) {
                writer.print(entry.getKey())
                writer.print("=")
                writer.println((entry.getValue() != null) ? entry.getValue().toString().replace("\\", "/") : "")
            }
        } finally {
            if (writer != null) {
                writer.close()
            }
        }
    }


    private void writeConfigDropinsServerVariables(File file, Properties varProps, Properties varProjectProps, boolean isDefaultVar) throws IOException, TransformerException, ParserConfigurationException {

        ServerConfigXmlDocument configDocument = ServerConfigXmlDocument.newInstance()

        configDocument.createComment(HEADER)

        for (Map.Entry entry : varProjectProps.entrySet()) {
            configDocument.createVariableWithValue(entry.getKey(), entry.getValue(), isDefaultVar)
        }

        for (Map.Entry entry : varProps.entrySet()) {
            configDocument.createVariableWithValue(entry.getKey(), entry.getValue(), isDefaultVar)
        }

        // write XML document to file
        makeParentDirectory(file)
        configDocument.writeXMLDocument(file)

    }

    private void makeParentDirectory(File file) {
        File parentDir = file.getParentFile()
        if (parentDir != null) {
            parentDir.mkdirs()
        }
    }

    @Internal
    protected String getPackagingType() throws Exception{
      if (project.plugins.hasPlugin("war") || !project.tasks.withType(War).isEmpty()) {
          if (project.plugins.hasPlugin("org.springframework.boot")) {
              return "springboot"
          }
          return "war"
      }
      else if (project.plugins.hasPlugin("ear") || !project.tasks.withType(Ear).isEmpty()) {
          return "ear"
      }
      else if (project.plugins.hasPlugin("org.springframework.boot") ) {
          return "springboot"
      }
      else {
          throw new GradleException("Archive path not found. Supported formats are jar, war, ear, and springboot jar.")
      }
    }

    //Checks if there is an app configured in an existing configDropins application xml file
    protected boolean hasConfiguredApp(File applicationXmlFile) {
      if (applicationXmlFile.exists()) {
          ApplicationXmlDocument appXml = new ApplicationXmlDocument()
          appXml.createDocument(applicationXmlFile)
          return appXml.hasChildElements()
      }
      return false
    }

    @Internal
    protected List getApplicationFilesFromConfiguration() {
        List appFiles = new ArrayList()

        //This loops thorugh all the Dependency objects that get created by the configuration
        for (Dependency dep : project.configurations.libertyApp.getDependencies()) {
            if (dep instanceof ModuleDependency) { //Check that dep isn't a File dependency
                dep.setTransitive(false) //Only want main artifacts, one for Maven and one or more for Gradle/Ivy dependencies
            }

            Set depArtifacts = project.configurations.libertyApp.files(dep) //Resolve the artifacts
            for (File depArtifact : depArtifacts) {
                File appFile = depArtifact
                if (dep instanceof ModuleDependency && server.stripVersion && depArtifact.getName().contains(dep.getVersion())) {
                    String noVersionName = depArtifact.getName().minus("-" + dep.getVersion()) //Assuming default Gradle naming scheme
                    File noVersionDependencyFile = new File(project.getLayout().getBuildDirectory().asFile.get(), 'libs/' + noVersionName) //Copying the file to build/libs with no version
                    FileUtils.copyFile(depArtifact, noVersionDependencyFile)
                    appFile = noVersionDependencyFile
                }
                if (FilenameUtils.getExtension(appFile.getName()).equals('war') || FilenameUtils.getExtension(appFile.getName()).equals('ear')) {
                    appFiles.add(appFile)
                }
            }
        }

        return appFiles
    }

    protected ServerTask createServerTask(Project project, String operation) {
        ServerTask serverTask =  new ServerTask()
        serverTask.setServerName(server.name)

        def installDir = getInstallDir(project)

        serverTask.setInstallDir(installDir)
        serverTask.setUserDir(getUserDir(project, installDir))

        serverTask.setOutputDir(new File(getOutputDir(project)))

        //Some uses of a server task do not require an operation to be specified
        if (operation != null && !operation.isEmpty()) {
            serverTask.setOperation(operation)
        }

        if (server.timeout != null && !server.timeout.isEmpty()) {
            serverTask.setTimeout(server.timeout)
        }

        return serverTask
    }

    protected Map getLibertyDirectoryPropertyFiles(String serverDirectoryParam) {

        File serverConfigDir = getServerDir(project)

        // if DevMode provides a server directory parameter use that for finding the server config dir
        if (serverDirectoryParam != null) {
            serverConfigDir = new File(serverDirectoryParam)
        }
        File wlpInstallDir = getInstallDir(project)
        File wlpUserDir = getUserDir(project, wlpInstallDir)

        return LibertyPropFilesUtility.getLibertyDirectoryPropertyFiles(new CommonLogger(project), wlpInstallDir, wlpUserDir, serverConfigDir)
    }

    // Return the loose application configuration xml file
    public File getLooseAppConfigFile(Boolean container, String appsDir) throws GradleException {
        String looseConfigFileName
        List appsList

        if (appsDir.equals("apps")) {
            appsList = server.deploy.apps
        } else if (appsDir.equals("dropins")) {
            appsList = server.deploy.dropins
        }

        Tuple applications = splitAppList(appsList)

        if (applications[0].isEmpty()) {
            throw new GradleException("Liberty dev mode requires a loose application file. No war/ear task was configured for 'apps' or 'dropins'. Unable to create a loose application file.")
        }

        applications[0].each { Task task ->
            looseConfigFileName = getLooseConfigFileName(task)
        }

        if (container) {
            File devcDestDir = new File(new File(project.getLayout().getBuildDirectory().getAsFile().get(), DevUtil.DEVC_HIDDEN_FOLDER), appsDir)
            return (new File(devcDestDir, looseConfigFileName));
        } else {
            File destDir = new File(getServerDir(project), appsDir)
            return (new File(destDir, looseConfigFileName));
        }
    }

}