| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more contributor license |
| * agreements. See the NOTICE file distributed with this work for additional information regarding |
| * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the License. You may obtain a |
| * copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software distributed under the License |
| * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
| * or implied. See the License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package org.apache.geode.session.tests; |
| |
| import static org.apache.geode.test.util.ResourceUtils.getResource; |
| import static org.assertj.core.api.Assertions.assertThat; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.net.URL; |
| import java.nio.file.Files; |
| import java.nio.file.Paths; |
| import java.util.HashMap; |
| import java.util.Properties; |
| import java.util.function.IntSupplier; |
| |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.transform.Transformer; |
| import javax.xml.transform.TransformerFactory; |
| import javax.xml.transform.dom.DOMSource; |
| import javax.xml.transform.stream.StreamResult; |
| |
| import org.apache.commons.io.FileUtils; |
| import org.apache.commons.io.FilenameUtils; |
| import org.apache.logging.log4j.Logger; |
| import org.codehaus.cargo.container.installer.Installer; |
| import org.codehaus.cargo.container.installer.ZipURLInstaller; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| |
| import org.apache.geode.logging.internal.log4j.api.LogService; |
| import org.apache.geode.management.internal.configuration.utils.ZipUtils; |
| |
| /** |
| * Base class for handling downloading and configuring J2EE containers. |
| * |
| * This class contains common logic for downloading and configuring J2EE containers with cargo, and |
| * some common methods for applying geode session replication configuration to those containers. |
| * |
| * Subclasses provide installation of specific containers. |
| */ |
| public abstract class ContainerInstall { |
| |
| private final IntSupplier portSupplier; |
| static final Logger logger = LogService.getLogger(); |
| static final String TMP_DIR = createTempDir(); |
| static final String GEODE_BUILD_HOME = System.getenv("GEODE_HOME"); |
| static final String GEODE_BUILD_HOME_LIB = GEODE_BUILD_HOME + "/lib/"; |
| private static final String DEFAULT_INSTALL_DIR = TMP_DIR + "/cargo_containers/"; |
| private static final String DEFAULT_MODULE_EXTRACTION_DIR = TMP_DIR + "/cargo_modules/"; |
| static final String DEFAULT_MODULE_LOCATION = GEODE_BUILD_HOME + "/tools/Modules/"; |
| |
| protected IntSupplier portSupplier() { |
| return portSupplier; |
| } |
| |
| private final ConnectionType connType; |
| private final String installPath; |
| private final String modulePath; |
| private final String warFilePath; |
| |
| private final String defaultLocatorAddress; |
| private int defaultLocatorPort; |
| |
| /** |
| * Represents the type of connection used in this installation |
| * |
| * Supports either PEER_TO_PEER or CLIENT_SERVER. Also containers several useful strings needed to |
| * identify XML files or connection types when setting up containers. |
| */ |
| public enum ConnectionType { |
| PEER_TO_PEER("peer-to-peer", "cache-peer.xml", false, false), |
| CLIENT_SERVER("client-server", "cache-client.xml", false, true), |
| CACHING_CLIENT_SERVER("client-server", "cache-client.xml", true, true); |
| |
| private final String name; |
| |
| private final String cacheXMLFileName; |
| |
| private final boolean enableLocalCache; |
| private final boolean isClientServer; |
| |
| ConnectionType(String name, String cacheXMLFileName, boolean enableLocalCache, |
| boolean isClientServer) { |
| this.name = name; |
| this.cacheXMLFileName = cacheXMLFileName; |
| this.enableLocalCache = enableLocalCache; |
| this.isClientServer = isClientServer; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| public String getCacheXMLFileName() { |
| return cacheXMLFileName; |
| } |
| |
| public boolean enableLocalCache() { |
| return enableLocalCache; |
| } |
| |
| public boolean isClientServer() { |
| return isClientServer; |
| } |
| } |
| |
| public ContainerInstall(String name, String downloadURL, ConnectionType connectionType, |
| String moduleName, IntSupplier portSupplier) throws IOException { |
| this(name, downloadURL, connectionType, moduleName, DEFAULT_MODULE_LOCATION, portSupplier); |
| } |
| |
| /** |
| * @param name used to name install directory |
| * @param connType Enum representing the connection type of this installation (either client |
| * server or peer to peer) |
| * @param moduleName The module name of the installation being setup (i.e. tomcat, appserver, |
| * etc.) |
| */ |
| public ContainerInstall(String name, String downloadURL, ConnectionType connType, |
| String moduleName, String geodeModuleLocation, IntSupplier portSupplier) throws IOException { |
| this.connType = connType; |
| this.portSupplier = portSupplier; |
| |
| String installDir = DEFAULT_INSTALL_DIR + name; |
| |
| clearPreviousInstall(installDir); |
| |
| String resource = getResource(getClass(), "/" + downloadURL).getPath(); |
| URL url = Paths.get(resource).toUri().toURL(); |
| logger.info("Installing container from URL " + url); |
| |
| // Optional step to install the container from a URL pointing to its distribution |
| Installer installer = |
| new ZipURLInstaller(url, TMP_DIR + "/downloads", installDir); |
| installer.install(); |
| |
| // Set install home |
| installPath = installer.getHome(); |
| // Find and extract the module path |
| modulePath = findAndExtractModule(geodeModuleLocation, moduleName); |
| logger.info("Extracted module " + moduleName + " to " + modulePath); |
| // Find the session testing war path |
| warFilePath = findSessionTestingWar(); |
| |
| // Default locator |
| defaultLocatorPort = 8080; |
| defaultLocatorAddress = "localhost"; |
| |
| logger.info("Installed container into " + getHome()); |
| } |
| |
| ServerContainer generateContainer(String containerDescriptors) throws IOException { |
| return generateContainer(null, containerDescriptors); |
| } |
| |
| /** |
| * Cleans up the installation by deleting the extracted module and downloaded installation folders |
| */ |
| private void clearPreviousInstall(String installDir) throws IOException { |
| File installFolder = new File(installDir); |
| // Remove installs from previous runs in the same folder |
| if (installFolder.exists()) { |
| logger.info("Deleting previous install folder " + installFolder.getAbsolutePath()); |
| FileUtils.deleteDirectory(installFolder); |
| } |
| } |
| |
| void setDefaultLocatorPort(int port) { |
| defaultLocatorPort = port; |
| } |
| |
| /** |
| * Whether the installation is client server |
| * |
| * Since an installation can only be client server or peer to peer there is no need for a function |
| * which checks for a peer to peer installation (just check if not client server). |
| */ |
| boolean isClientServer() { |
| return connType.isClientServer(); |
| } |
| |
| /** |
| * Where the installation is located |
| */ |
| public String getHome() { |
| return installPath; |
| } |
| |
| /** |
| * Where the module is located |
| * |
| * The module contains jars needed for geode session setup as well as default templates for some |
| * needed XML files. |
| */ |
| String getModulePath() { |
| return modulePath; |
| } |
| |
| /** |
| * The path to the session testing WAR file |
| */ |
| String getWarFilePath() { |
| return warFilePath; |
| } |
| |
| /** |
| * @return The enum {@link #connType} which represents the type of connection for this |
| * installation |
| */ |
| ConnectionType getConnectionType() { |
| return connType; |
| } |
| |
| /** |
| * Gets the {@link #defaultLocatorAddress} |
| * |
| * This is the address that a container uses by default. Containers themselves can have their own |
| * personal locator address, but will default to this address unless specifically set. |
| */ |
| String getDefaultLocatorAddress() { |
| return defaultLocatorAddress; |
| } |
| |
| /** |
| * Gets the {@link #defaultLocatorPort} |
| * |
| * This is the port that a container uses by default. Containers themselves can have their own |
| * personal locator port, but will default to this port unless specifically set. |
| */ |
| int getDefaultLocatorPort() { |
| return defaultLocatorPort; |
| } |
| |
| /** |
| * Gets the cache XML file to use by default for this installation |
| */ |
| File getCacheXMLFile() { |
| return new File(modulePath + "/conf/" + getConnectionType().getCacheXMLFileName()); |
| } |
| |
| /** |
| * Cargo specific string to identify the container with |
| */ |
| public abstract String getInstallId(); |
| |
| /** |
| * A human readable description of the installation |
| */ |
| public abstract String getInstallDescription(); |
| |
| /** |
| * Get the session manager class to use |
| */ |
| public abstract String getContextSessionManagerClass(); |
| |
| /** |
| * Generates a {@link ServerContainer} from the given {@link ContainerInstall} |
| * |
| * @param containerDescriptors Additional descriptors used to identify a container |
| */ |
| public abstract ServerContainer generateContainer(File containerConfigHome, |
| String containerDescriptors) throws IOException; |
| |
| /** |
| * Get the path to the session testing war by walking up directories to the correct folder. |
| * |
| * NOTE::This walks into the extensions folder and then uses a hardcoded path from there making it |
| * very unreliable if things are moved. |
| */ |
| private static String findSessionTestingWar() { |
| // Start out searching directory above current |
| String curPath = "../"; |
| |
| // Looking for extensions folder |
| final String warModuleDirName = "extensions"; |
| File warModuleDir = null; |
| |
| // While directory searching for is not found |
| while (warModuleDir == null) { |
| // Try to find the find the directory in the current directory |
| File[] files = new File(curPath).listFiles(); |
| for (File file : files) { |
| if (file.isDirectory() && file.getName().equals(warModuleDirName)) { |
| warModuleDir = file; |
| break; |
| } |
| } |
| |
| // Keep moving up until you find it |
| curPath += "../"; |
| } |
| |
| // Return path to extensions plus hardcoded path from there to the WAR |
| return warModuleDir.getAbsolutePath() |
| + "/session-testing-war/build/libs/session-testing-war.war"; |
| } |
| |
| /** |
| * Finds and extracts the geode module associated with the specified module. |
| * |
| * @param moduleName The module name (i.e. tomcat, appserver, etc.) of the module that should be |
| * extract. Used as a search parameter to find the module archive. |
| * @return The path to the non-archive (extracted) version of the module files |
| */ |
| private static String findAndExtractModule(String geodeModuleLocation, String moduleName) |
| throws IOException { |
| File modulesDir = new File(geodeModuleLocation); |
| |
| logger.info("Trying to access build dir " + modulesDir); |
| |
| // Search directory for tomcat module folder/zip |
| boolean archive = false; |
| File modulePath = null; |
| for (File file : modulesDir.listFiles()) { |
| |
| if (file.getName().toLowerCase().contains(moduleName)) { |
| modulePath = file; |
| |
| archive = !file.isDirectory(); |
| if (!archive) { |
| break; |
| } |
| } |
| } |
| |
| assertThat(modulePath).describedAs("module path").isNotNull(); |
| |
| String extractedModulePath = |
| modulePath.getName().substring(0, modulePath.getName().length() - 4); |
| // Get the name of the new module folder within the extraction directory |
| File newModuleFolder = new File(DEFAULT_MODULE_EXTRACTION_DIR + extractedModulePath); |
| // Remove any previous module folders extracted here |
| if (newModuleFolder.exists()) { |
| logger.info("Deleting previous modules directory " + newModuleFolder.getAbsolutePath()); |
| FileUtils.deleteDirectory(newModuleFolder); |
| } |
| |
| // Unzip if it is a zip file |
| if (archive) { |
| if (!FilenameUtils.getExtension(modulePath.getAbsolutePath()).equals("zip")) { |
| throw new IOException("Bad module archive " + modulePath); |
| } |
| |
| // Extract folder to location if not already there |
| if (!newModuleFolder.exists()) { |
| ZipUtils.unzip(modulePath.getAbsolutePath(), newModuleFolder.getAbsolutePath()); |
| } |
| |
| modulePath = newModuleFolder; |
| } |
| |
| // No module found within directory throw IOException |
| if (modulePath == null) { |
| throw new IOException("No module found in " + modulesDir); |
| } |
| return modulePath.getAbsolutePath(); |
| } |
| |
| /** |
| * Edits the specified property within the given property file |
| * |
| * @param filePath path to the property file |
| * @param propertyName property name to edit |
| * @param propertyValue new property value |
| * @param append whether or not to append the given property value. If true appends the given |
| * property value the current value. If false, replaces the current property value with the |
| * given property value |
| */ |
| static void editPropertyFile(String filePath, String propertyName, String propertyValue, |
| boolean append) throws Exception { |
| FileInputStream input = new FileInputStream(filePath); |
| Properties properties = new Properties(); |
| properties.load(input); |
| |
| String val; |
| if (append) { |
| val = properties.getProperty(propertyName) + propertyValue; |
| } else { |
| val = propertyValue; |
| } |
| |
| properties.setProperty(propertyName, val); |
| properties.store(new FileOutputStream(filePath), null); |
| |
| logger.info("Modified container Property file " + filePath); |
| } |
| |
| static void editXMLFile(String XMLPath, String tagId, String tagName, |
| String parentTagName, HashMap<String, String> attributes) { |
| editXMLFile(XMLPath, tagId, tagName, tagName, parentTagName, attributes, false); |
| } |
| |
| static void editXMLFile(String XMLPath, String tagName, String parentTagName, |
| HashMap<String, String> attributes, boolean writeOnSimilarAttributeNames) { |
| editXMLFile(XMLPath, null, tagName, tagName, parentTagName, attributes, |
| writeOnSimilarAttributeNames); |
| } |
| |
| /** |
| * Edit the given xml file |
| * |
| * Uses {@link #findNodeWithAttribute(Document, String, String, String)}, |
| * {@link #rewriteNodeAttributes(Node, HashMap)}, |
| * {@link #nodeHasExactAttributes(Node, HashMap, boolean)} to edit the required parts of the XML |
| * file. |
| * |
| * @param XMLPath The path to the xml file to edit |
| * @param tagId The id of tag to edit. If null, then this method will add a new xml element, |
| * unless writeOnSimilarAttributeNames is set to true. |
| * @param tagName The name of the xml element to edit |
| * @param replacementTagName The new name of the XML attribute that is being edited |
| * @param parentTagName The parent element of the element we should edit |
| * @param attributes the xml attributes for the element to edit |
| * @param writeOnSimilarAttributeNames If true, find an existing element with the same set of |
| * attributes as the attributes parameter, and modifies the attributes of that element, |
| * rather than adding a new element. If false, create a new XML element (unless tagId is |
| * not null). |
| */ |
| private static void editXMLFile(String XMLPath, String tagId, String tagName, |
| String replacementTagName, String parentTagName, HashMap<String, String> attributes, |
| boolean writeOnSimilarAttributeNames) { |
| |
| try { |
| // Get XML file to edit |
| DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); |
| DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); |
| Document doc = docBuilder.parse(XMLPath); |
| |
| Node node = null; |
| // Get node with specified tagId |
| if (tagId != null) { |
| node = findNodeWithAttribute(doc, tagName, "id", tagId); |
| } |
| // If writing on similar attributes then search by tag name |
| else if (writeOnSimilarAttributeNames) { |
| // Get all the nodes with the given tag name |
| NodeList nodes = doc.getElementsByTagName(tagName); |
| for (int i = 0; i < nodes.getLength(); i++) { |
| Node n = nodes.item(i); |
| // If the node being iterated across has the exact attributes then it is the one that |
| // should be edited |
| if (nodeHasExactAttributes(n, attributes, false)) { |
| node = n; |
| break; |
| } |
| } |
| } |
| // If a node if found |
| if (node != null) { |
| doc.renameNode(node, null, replacementTagName); |
| // Rewrite the node attributes |
| rewriteNodeAttributes(node, attributes); |
| // Write the tagId so that it can be found easier next time |
| if (tagId != null) |
| ((Element) node).setAttribute("id", tagId); |
| } |
| // No node found creates new element under the parent tag passed in |
| else { |
| Element e = doc.createElement(replacementTagName); |
| // Set id attribute |
| if (tagId != null) { |
| e.setAttribute("id", tagId); |
| } |
| // Set other attributes |
| for (String key : attributes.keySet()) { |
| e.setAttribute(key, attributes.get(key)); |
| } |
| |
| // Add it as a child of the tag for the file |
| doc.getElementsByTagName(parentTagName).item(0).appendChild(e); |
| } |
| |
| // Write updated XML file |
| TransformerFactory transformerFactory = TransformerFactory.newInstance(); |
| Transformer transformer = transformerFactory.newTransformer(); |
| DOMSource source = new DOMSource(doc); |
| StreamResult result = new StreamResult(new File(XMLPath)); |
| transformer.transform(source, result); |
| |
| logger.info("Modified container XML file " + XMLPath); |
| } catch (Exception e) { |
| throw new RuntimeException("Unable to edit XML file", e); |
| } |
| } |
| |
| /** |
| * Finds the node in the given document with the given name and attribute |
| * |
| * @param doc XML document to search for the node |
| * @param nodeName The name of the node to search for |
| * @param name The name of the attribute that the node should contain |
| * @param value The value of the node's given attribute |
| * @return Node with the given name, attribute, and attribute value |
| */ |
| private static Node findNodeWithAttribute(Document doc, String nodeName, String name, |
| String value) { |
| // Get all nodes with given name |
| NodeList nodes = doc.getElementsByTagName(nodeName); |
| if (nodes == null) { |
| return null; |
| } |
| |
| // Find and return the first node that has the given attribute |
| for (int i = 0; i < nodes.getLength(); i++) { |
| Node node = nodes.item(i); |
| Node nodeAttr = node.getAttributes().getNamedItem(name); |
| |
| if (nodeAttr != null && nodeAttr.getTextContent().equals(value)) { |
| return node; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Replaces the node's attributes with the attributes in the given hashmap |
| * |
| * @param node XML node that should be edited |
| * @param attributes HashMap of strings representing the attributes of a node (key = value) |
| * @return The given node with ONLY the given attributes |
| */ |
| private static Node rewriteNodeAttributes(Node node, HashMap<String, String> attributes) { |
| NamedNodeMap nodeAttrs = node.getAttributes(); |
| |
| // Remove all previous attributes |
| while (nodeAttrs.getLength() > 0) { |
| nodeAttrs.removeNamedItem(nodeAttrs.item(0).getNodeName()); |
| } |
| |
| // Set to new attributes |
| for (String key : attributes.keySet()) { |
| ((Element) node).setAttribute(key, attributes.get(key)); |
| } |
| |
| return node; |
| } |
| |
| /** |
| * Checks to see whether the given XML node has the exact attributes given in the attributes |
| * hashmap |
| * |
| * @param checkSimilarValues If true, will also check to make sure that the given node's |
| * attributes also have the exact same values as the ones given in the attributes HashMap. |
| * @return True if the node has only the attributes the are given by the HashMap (no more and no |
| * less attributes). If {@param checkSimilarValues} is true then only returns true if the |
| * node shares attributes with the given attribute list exactly. |
| */ |
| private static boolean nodeHasExactAttributes(Node node, HashMap<String, String> attributes, |
| boolean checkSimilarValues) { |
| NamedNodeMap nodeAttrs = node.getAttributes(); |
| |
| // Check to make sure the node has all attribute fields |
| for (String key : attributes.keySet()) { |
| Node attr = nodeAttrs.getNamedItem(key); |
| if (attr == null |
| || checkSimilarValues && !attr.getTextContent().equals(attributes.get(key))) { |
| return false; |
| } |
| } |
| |
| // Check to make sure the node does not have more than the attribute fields |
| for (int i = 0; i < nodeAttrs.getLength(); i++) { |
| String attr = nodeAttrs.item(i).getNodeName(); |
| if (attributes.get(attr) == null || checkSimilarValues |
| && !attributes.get(attr).equals(nodeAttrs.item(i).getTextContent())) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| private static String createTempDir() { |
| try { |
| return Files.createTempDirectory("geode_container_install") |
| .toAbsolutePath() |
| .toString(); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| } |