/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2014 ForgeRock AS.
*/
package org.forgerock.openidm.patch.utils;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ConnectException;
import java.net.Socket;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.forgerock.openidm.patch.Archive;
import org.forgerock.openidm.patch.exception.PatchException;
/**
* Basic functionality to support patching logic, including static replacement of artifacts,
* support for replacement or changing through logic.
*/
public class PatchUtil {
private final ZipUtil zipUtil = new ZipUtil();
private final FileUtil fileUtil = new FileUtil();
private final Archive archive = Archive.getInstance();
private final static Logger logger = Logger.getLogger("PatchLog");
/**
* Access to the raw zip utility functionality.
* @return the zip utility
*/
public ZipUtil getZipUtil() {
return zipUtil;
}
/**
* Access to the raw file utility functionality.
* @return the file utility
*/
public FileUtil getFileUtil() {
return fileUtil;
}
/**
* Replaces installation artifacts with artifacts in the patch, under
* the "files" directory. Target files use the same path as the name that
* occurs in the patch, relative to that "files" directory.
*
* @param patchUrl the URL of the patch JAR file
* @param installDir the directory containing the files to replace
* @throws IOException Indicates a failure while extracting files from the patch bundle
*/
public void replaceStaticFiles(URL patchUrl, File installDir) throws IOException {
zipUtil.extractFolder(patchUrl, Archive.STATIC_FILES_ENTRY, installDir);
}
/**
* Replaces multiple files matching corresponding name patterns with the
* specified files.
*
* @param patchUrl The patch file url
* @param targetDir The target directory
* @param files Array of file names and corresponding matching patterns
* @param fileMustExist Whether The file must exist in the target directory
* @throws IOException Indicates a failure while extracting files from the patch bundle
*/
public void replaceFilesWithPattern(URL patchUrl, File targetDir, String[][] files,
boolean fileMustExist) throws IOException {
for (String[] file : files) {
replaceFileWithPattern(patchUrl, new File(targetDir, file[0]), file[1], file[2], fileMustExist);
}
}
/**
* Replace a file matching a given name pattern with the specified file.
* For example replace a file with a given version in the name
* with a file of a different version.
*
* @param patchUrl The URL of the patch JAR file
* @param fileDir The directory in which the file to be replaced is located
* @param fileNameRegex A regular expression to match against the file to replace
* @param patchFilePath The file to be extracted from the patch bundle
* @param fileMustExist Whether The file must exist in the target directory
* @throws IOException Indicates a failure while extracting files from the patch bundle
*/
public void replaceFileWithPattern(URL patchUrl, File fileDir, String fileNameRegex, String patchFilePath,
boolean fileMustExist) throws IOException {
File fileToReplace = FileUtil.find(fileDir, fileNameRegex);
if (fileToReplace == null) {
if (fileMustExist) {
// TODO: better exception
throw new RuntimeException("Precondition failed: file to replace in "
+ fileDir + " with pattern " + fileNameRegex + " does not exist");
}
} else {
// TODO: move instead; should we also check the patch file exists before moving/removing?
archive.insert(fileToReplace);
fileUtil.delete(fileToReplace, false);
}
File fileToExtract = new File(patchFilePath);
// Assume same directory as the file to replace, but with the name of the artifact in the patch
File targetFile = new File(fileDir, fileToExtract.getName());
zipUtil.extractFile(patchUrl, fileToExtract, targetFile);
}
/**
* Determine if a process matching the given name pattern is running.
*
* @param processNameRegex Regular expression used to match against the running process
* @return true or false depending on if the process is running.
* @throws PatchException if the underlying OS mechanism for obtaining the
* running processes fails.
*/
public boolean isProcessRunning(String processNameRegex) throws PatchException {
String os = System.getProperty("os.name").toLowerCase();
String cmd = "/bin/ps -e";
boolean match = false;
// Determine if we're on Windows
if (os.indexOf("win") >= 0) {
//TODO: Figure out how to do this on Windows
//cmd = System.getenv("windir") +"\\system32\\"+"tasklist.exe";
return false;
}
try {
String line;
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader input =
new BufferedReader(new InputStreamReader(p.getInputStream()));
while ((line = input.readLine()) != null) {
match = line.matches(processNameRegex);
logger.log(Level.FINEST, "Comparing process {0} : {1}", new Object[]{line, processNameRegex});
if (match) {
break;
}
}
input.close();
} catch (IOException ex) {
throw new PatchException("Unable to determine if process matching " + processNameRegex
+ " is running.", ex);
}
return match;
}
/**
* Compare two string representation of dot notation version numbers.
* @param v1 First version number to be compared
* @param v2 Second version number to be compared
* @return 0 if the versions are equal
* -1 if v1 is less than v2
* 1 if v1 is greater than v2
*/
public int comapreVersionNumbers(String v1, String v2) {
String[] t1 = v1.split("\\.");
String[] t2 = v2.split("\\.");
if (v1.equalsIgnoreCase(v2)) {
return 0;
}
for (int i = 0; i < t1.length; i++) {
if ((i >= t2.length) || (new Integer(t2[i]) < new Integer(t1[i]))) {
return 1;
} else if (new Integer(t2[i]) > new Integer(t1[i])) {
return -1;
}
}
return 0;
}
/**
* Test if a process is listening on the specified host/port.
*
* @param host Host name or IP address of the host
* @param port Port to be checked
* @return true if a process is accepting connections on the specified host/port, false otherwise.
* @throws IOException if an I/O error occurs when creating the socket.
*/
public boolean isSocketListening(String host, int port) throws IOException {
Socket s = null;
try {
s = new Socket(host, port);
} catch (ConnectException ignore) {
//ignore
}
return (s != null);
}
}