/******************************************************************************* * Copyright (c) 2018, 2019 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.microshed.boost.runtimes.openliberty; import static org.microshed.boost.common.config.ConfigConstants.*; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.microshed.boost.common.BoostLoggerI; import org.microshed.boost.common.boosters.JDBCBoosterConfig; import org.microshed.boost.common.config.BoostProperties; import org.microshed.boost.common.utils.BoostUtil; import org.w3c.dom.Document; import org.w3c.dom.Element; import io.openliberty.tools.common.plugins.util.OSUtil; /** * Create a Liberty server.xml * */ public class LibertyServerConfigGenerator { public static final String CONFIG_DROPINS_DIR = "/configDropins/defaults"; private final String serverPath; private final String libertyInstallPath; private final String encryptionKey; private final BoostLoggerI logger; private Document serverXml; private Element featureManager; private Element serverRoot; private Element httpEndpoint; private Document variablesXml; private Element variablesRoot; private Set<String> featuresAdded; public LibertyServerConfigGenerator(String serverPath, String encryptionKey, BoostLoggerI logger) throws ParserConfigurationException { this.serverPath = serverPath; this.libertyInstallPath = serverPath + "/../../.."; // Three directories // back from // 'wlp/usr/servers/defaultServer' this.encryptionKey = encryptionKey; this.logger = logger; generateServerXml(); generateVariablesXml(); featuresAdded = new HashSet<String>(); } private void generateServerXml() throws ParserConfigurationException { DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); // Create top level server config element serverXml = docBuilder.newDocument(); serverRoot = serverXml.createElement("server"); serverRoot.setAttribute("description", "Liberty server generated by Boost"); serverXml.appendChild(serverRoot); // Create featureManager config element featureManager = serverXml.createElement(FEATURE_MANAGER); serverRoot.appendChild(featureManager); // Create httpEndpoint config element httpEndpoint = serverXml.createElement(HTTP_ENDPOINT); httpEndpoint.setAttribute("id", DEFAULT_HTTP_ENDPOINT); serverRoot.appendChild(httpEndpoint); } private void generateVariablesXml() throws ParserConfigurationException { DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); // Create top level server config element variablesXml = docBuilder.newDocument(); variablesRoot = variablesXml.createElement("server"); variablesRoot.setAttribute("description", "Boost variables"); variablesXml.appendChild(variablesRoot); } /** * Add a Liberty feature to the server configuration * */ public void addFeature(String featureName) { if (!featuresAdded.contains(featureName)) { Element feature = serverXml.createElement(FEATURE); feature.appendChild(serverXml.createTextNode(featureName)); featureManager.appendChild(feature); featuresAdded.add(featureName); } } /** * Add a list of features to the server configuration * */ public void addFeatures(List<String> features) { for (String featureName : features) { addFeature(featureName); } } /** * Write the server.xml and bootstrap.properties to the server config * directory * * @throws TransformerException * @throws IOException */ public void writeToServer() throws TransformerException, IOException { TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); // Replace auto-generated server.xml DOMSource server = new DOMSource(serverXml); StreamResult serverResult = new StreamResult(new File(serverPath + "/server.xml")); transformer.transform(server, serverResult); // Create configDropins/default path Path configDropins = Paths.get(serverPath + CONFIG_DROPINS_DIR); Files.createDirectories(configDropins); // Write variables.xml to configDropins DOMSource variables = new DOMSource(variablesXml); StreamResult variablesResult = new StreamResult(new File(serverPath + CONFIG_DROPINS_DIR + "/variables.xml")); transformer.transform(variables, variablesResult); } public void addConfigVariables(Properties properties) throws IOException { if (properties != null) { for (String key : properties.stringPropertyNames()) { String value = properties.getProperty(key); addConfigVariable(key, value); } } } private void addConfigVariable(String key, String value) throws IOException { // Using this to hold the properties we want to encrypt and the type of // encryption we want to use Map<String, String> propertiesToEncrypt = BoostProperties.getPropertiesToEncrypt(); if (propertiesToEncrypt.containsKey(key) && value != null && !value.equals("")) { value = encrypt(key, value); } Element variable = variablesXml.createElement("variable"); variable.setAttribute("name", key); variable.setAttribute("defaultValue", value); variablesRoot.appendChild(variable); } public void addKeystore(Map<String, String> keystoreProps, Map<String, String> keyProps) { Element keystore = serverXml.createElement(KEYSTORE); keystore.setAttribute("id", DEFAULT_KEYSTORE); for (String key : keystoreProps.keySet()) { keystore.setAttribute(key, keystoreProps.get(key)); } if (!keyProps.isEmpty()) { Element keyEntry = serverXml.createElement(KEY_ENTRY); for (String key : keyProps.keySet()) { keyEntry.setAttribute(key, keyProps.get(key)); } keystore.appendChild(keyEntry); } serverRoot.appendChild(keystore); } public void addApplication(String appName) { Element appCfg = serverXml.createElement(APPLICATION); appCfg.setAttribute(CONTEXT_ROOT, "/"); appCfg.setAttribute(LOCATION, appName + "." + WAR_PKG_TYPE); appCfg.setAttribute(TYPE, WAR_PKG_TYPE); serverRoot.appendChild(appCfg); } public void addHostname(String hostname) throws Exception { httpEndpoint.setAttribute("host", BoostUtil.makeVariable(BoostProperties.ENDPOINT_HOST)); addConfigVariable(BoostProperties.ENDPOINT_HOST, hostname); } public void addHttpPort(String httpPort) throws Exception { httpEndpoint.setAttribute("httpPort", BoostUtil.makeVariable(BoostProperties.ENDPOINT_HTTP_PORT)); addConfigVariable(BoostProperties.ENDPOINT_HTTP_PORT, httpPort); } public void addHttpsPort(String httpsPort) throws Exception { httpEndpoint.setAttribute("httpsPort", BoostUtil.makeVariable(BoostProperties.ENDPOINT_HTTPS_PORT)); addConfigVariable(BoostProperties.ENDPOINT_HTTPS_PORT, httpsPort); } public Document getServerXmlDoc() { return serverXml; } private String getSecurityUtilCmd(String libertyInstallPath) { if (OSUtil.isWindows()) { return libertyInstallPath + "/bin/securityUtility.bat"; } else { return libertyInstallPath + "/bin/securityUtility"; } } private String encrypt(String propertyKey, String propertyValue) throws IOException { // Won't encode the property if it contains the aes flag if (!isEncoded(propertyValue)) { Runtime rt = Runtime.getRuntime(); List<String> commands = new ArrayList<String>(); commands.add(getSecurityUtilCmd(libertyInstallPath)); commands.add("encode"); commands.add(propertyValue); // Get the internal encryption type set for this property String encryptionType = BoostProperties.getPropertiesToEncrypt().get(propertyKey); if (encryptionType != null && !encryptionType.equals("")) { commands.add("--encoding=" + encryptionType); } else { commands.add("--encoding=aes"); } // Set the defined encryption key if (encryptionKey != null && !encryptionKey.equals("")) { commands.add("--key=" + encryptionKey); } Process proc = rt.exec(commands.toArray(new String[0])); BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream())); BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream())); String s = null; StringBuilder out = new StringBuilder(); while ((s = stdInput.readLine()) != null) { out.append(s); } StringBuilder error = new StringBuilder(); while ((s = stdError.readLine()) != null) { error.append(s + "\n"); } if (error.length() != 0) { throw new IOException("Password encryption failed: " + error); } return out.toString(); } return propertyValue; } public boolean isEncoded(String property) { return property.contains("{aes}") || property.contains("{hash}") || property.contains("{xor}"); } public void addDataSource(Map<String, String> driverInfo, Properties datasourceProperties) throws Exception { String datasourcePropertiesElement = null; String driverName = driverInfo.get(JDBCBoosterConfig.DRIVER_NAME); if (driverName.equals(JDBCBoosterConfig.DERBY_DRIVER_NAME)) { datasourcePropertiesElement = PROPERTIES_DERBY_EMBEDDED; } else if (driverName.equals(JDBCBoosterConfig.DB2_DRIVER_NAME)) { datasourcePropertiesElement = PROPERTIES_DB2_JCC; } else if (driverName.equals(JDBCBoosterConfig.MYSQL_DRIVER_NAME)) { datasourcePropertiesElement = PROPERTIES; } else if (driverName.equals(JDBCBoosterConfig.POSTGRESQL_DRIVER_NAME)) { datasourcePropertiesElement = PROPERTIES_POSTGRESQL; } // Add library Element lib = serverXml.createElement(LIBRARY); lib.setAttribute("id", JDBC_LIBRARY_1); Element fileLoc = serverXml.createElement(FILESET); fileLoc.setAttribute("dir", RESOURCES); fileLoc.setAttribute("includes", driverInfo.get(JDBCBoosterConfig.DRIVER_JAR)); lib.appendChild(fileLoc); serverRoot.appendChild(lib); // Add datasource Element dataSource = serverXml.createElement(DATASOURCE); dataSource.setAttribute("id", DEFAULT_DATASOURCE); dataSource.setAttribute(JDBC_DRIVER_REF, JDBC_DRIVER_1); // Add all configured datasource properties Element props = serverXml.createElement(datasourcePropertiesElement); addDatasourceProperties(datasourceProperties, props); dataSource.appendChild(props); serverRoot.appendChild(dataSource); // Add jdbc driver Element jdbcDriver = serverXml.createElement(JDBC_DRIVER); jdbcDriver.setAttribute("id", JDBC_DRIVER_1); jdbcDriver.setAttribute(LIBRARY_REF, JDBC_LIBRARY_1); serverRoot.appendChild(jdbcDriver); // Add variables addConfigVariables(datasourceProperties); } private void addDatasourceProperties(Properties serverProperties, Element propertiesElement) { for (String property : serverProperties.stringPropertyNames()) { String attribute = property.replace(BoostProperties.DATASOURCE_PREFIX, ""); propertiesElement.setAttribute(attribute, BoostUtil.makeVariable(property)); } } public void addElementWithAttributes(String elementName, Map<String, String> attributes) { Element element = serverXml.createElement(elementName); for (String attributeName : attributes.keySet()) { element.setAttribute(attributeName, attributes.get(attributeName)); } serverRoot.appendChild(element); } }