Java tutorial
/* * 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; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.util.LinkedHashMap; import java.util.Map; import java.util.logging.ConsoleHandler; import java.util.logging.FileHandler; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.PropertiesConfiguration; import org.forgerock.openidm.patch.exception.PatchException; import static org.forgerock.openidm.patch.utils.PatchConstants.*; import org.forgerock.openidm.patch.utils.SingleLineFormatter; /** * The Main class for the patch framework. */ public final class Main { private final static PropertiesConfiguration CONFIG = new PropertiesConfiguration(); private final static Logger logger = Logger.getLogger("PatchLog"); private final static Logger historyLogger = Logger.getLogger("HistoryLog"); private static final String PATCH_DIR = "patch"; private static final String PATCH_ARCHIVE_DIR = PATCH_DIR + File.separator + "archive"; private static final String PATCH_HISTORY_FILE = PATCH_DIR + File.separator + "history.log"; private static final String PATCH_LOG_FILE = "patch.log"; private Main() { // Prevent instantiation. } /** * Execute the patch bundle. * * @param args Expects args[0] to contain the target installation directory * to be patched. Optionally takes a '-w' parameter specifying the working directory. * @throws PatchException Thrown if the patch fails to apply correctly */ public static void main(String[] args) throws PatchException { if (args != null && args.length > 0) { String installDir = args[0]; Map<String, Object> params = parseOptions(args); String workingDir = optionValueStr(params, "w", installDir); File w = new File(workingDir); File i = new File(installDir); storePatchBundle(w, i, params); try { URL url = getPatchLocation(); execute(url, url.toString(), w, i, params); } catch (IOException ex) { Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); } } else { printUsage(args); } } /** * Executes the specified patch bundle. * * @param patchUrl A URL specifying the location of the patch bundle * @param originalUrlString The String representation of the patch bundle location * @param workingDir The working directory in which store logs and temporary files * @param installDir The target directory against which the patch is to be applied * @param params Additional patch specific parameters * @throws IOException Thrown in the event of a failure creating the patch archive * or log files */ public static void execute(URL patchUrl, String originalUrlString, File workingDir, File installDir, Map<String, Object> params) throws IOException { try { // Load the base patch configuration InputStream in = CONFIG.getClass().getResourceAsStream(CONFIG_PROPERTIES_FILE); if (in == null) { throw new PatchException( "Unable to locate: " + CONFIG_PROPERTIES_FILE + " in: " + patchUrl.toString()); } else { CONFIG.load(in); } // Configure logging and disable parent handlers SingleLineFormatter formatter = new SingleLineFormatter(); Handler historyHandler = new FileHandler(workingDir + File.separator + PATCH_HISTORY_FILE, true); Handler consoleHandler = new ConsoleHandler(); consoleHandler.setFormatter(formatter); historyHandler.setFormatter(formatter); historyLogger.setUseParentHandlers(false); historyLogger.addHandler(consoleHandler); historyLogger.addHandler(historyHandler); // Initialize the Archive Archive archive = Archive.getInstance(); archive.initialize(installDir, new File(workingDir, PATCH_ARCHIVE_DIR), CONFIG.getString(PATCH_BACKUP_ARCHIVE)); // Create the patch logger once we've got the archive directory Handler logHandler = new FileHandler(archive.getArchiveDirectory() + File.separator + PATCH_LOG_FILE, false); logHandler.setFormatter(formatter); logger.setUseParentHandlers(false); logger.addHandler(logHandler); // Instantiate the patcgh implementation and invoke the patch Patch patch = instantiatePatch(); patch.initialize(patchUrl, originalUrlString, workingDir, installDir, params); historyLogger.log(Level.INFO, "Applying {0}, version={1}", new Object[] { CONFIG.getProperty(PATCH_DESCRIPTION), CONFIG.getProperty(PATCH_RELEASE) }); historyLogger.log(Level.INFO, "Target: {0}, Source: {1}", new Object[] { installDir, patchUrl }); patch.apply(); historyLogger.log(Level.INFO, "Completed"); } catch (PatchException pex) { historyLogger.log(Level.SEVERE, "Failed", pex); } catch (ConfigurationException ex) { historyLogger.log(Level.SEVERE, "Failed to load patch configuration", ex); } finally { try { Archive.getInstance().close(); } catch (IOException ex) { historyLogger.log(Level.SEVERE, "Failed to close patch archive", ex); } } } private static URL getPatchLocation() { return Main.class.getProtectionDomain().getCodeSource().getLocation(); } private static Patch instantiatePatch() throws PatchException { Object obj = null; try { String patchClass = CONFIG.getString(CONFIG_PATCH_IMPL_CLASS); if (patchClass == null) { throw new PatchException("Invalid configuration, " + CONFIG_PATCH_IMPL_CLASS + " not specified."); } Class c = Class.forName(patchClass); obj = c.newInstance(); } catch (SecurityException ex) { logger.log(Level.SEVERE, null, ex); throw new PatchException(ex.getMessage(), ex); } catch (InstantiationException ex) { logger.log(Level.SEVERE, null, ex); throw new PatchException(ex.getMessage(), ex); } catch (IllegalAccessException ex) { logger.log(Level.SEVERE, null, ex); throw new PatchException(ex.getMessage(), ex); } catch (ClassNotFoundException ex) { Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); } return (Patch) obj; } private static void storePatchBundle(File workingDir, File installDir, Map<String, Object> params) throws PatchException { URL url = getPatchLocation(); // Download the patch file ReadableByteChannel channel = null; try { channel = Channels.newChannel(url.openStream()); } catch (IOException ex) { throw new PatchException("Failed to access the specified file " + url + " " + ex.getMessage(), ex); } String targetFileName = new File(url.getPath()).getName(); File patchDir = new File(workingDir, "patch/bin"); patchDir.mkdirs(); File targetFile = new File(patchDir, targetFileName); FileOutputStream fos = null; try { fos = new FileOutputStream(targetFile); } catch (FileNotFoundException ex) { throw new PatchException("Error in getting the specified file to " + targetFile, ex); } try { fos.getChannel().transferFrom(channel, 0, Long.MAX_VALUE); System.out.println("Downloaded to " + targetFile); } catch (IOException ex) { throw new PatchException("Failed to get the specified file " + url + " to: " + targetFile, ex); } } /** * Prints basic command line usage to system out. * * @param args command line args */ private static void printUsage(String[] args) { System.out.println("Usage: <install dir> <options>"); System.out.println("Where options are:"); System.out.println("-w <working dir>"); System.out.println("<patch specific options>"); } /** * Parse all command line arguments into an options map Assumes that options * are in the form of either -<option key> value or -<option key>. * * @param args the command line arguments * @return the parsed options map with option key value pairs. Options with * key only have a null value */ private static Map<String, Object> parseOptions(String[] args) { Map<String, Object> params = new LinkedHashMap<String, Object>(); for (int count = 1; count < args.length; count++) { String key = args[count]; if (key != null && key.startsWith("-")) { key = key.substring(1); String value = null; int nextArgIdx = count + 1; if (nextArgIdx < args.length) { String nextArg = args[nextArgIdx]; if (nextArg != null && !nextArg.startsWith("-")) { count++; value = nextArg; } } params.put(key, value); } } return params; } /** * Gets the String value (can be null) of an option. * * @param params all command line parameters * @param key the option key * @param defaultValue the default value to assign if the option is not set. * if the option is set, but is set to null, the default value is not used * @return the value if the entry is present, or the default value if it is * not * @throws InvalidArgsException if the option is not of String type */ private static String optionValueStr(Map<String, Object> params, String key, String defaultValue) { Object value = optionValue(params, key, defaultValue); if (value == null) { return null; } else if (value instanceof String) { return (String) value; } else { throw new IllegalArgumentException("The value for " + key + " is invalid, expected String"); } } /** * Gets the value (can be null) of an option. * * @param params all command line parameters * @param key the option key * @param defaultValue the default value to assign if the option is not set. * if the option is set, but is set to null, the default value is not used * @return the value if the entry is present, or the default value if it is * not */ private static Object optionValue(Map<String, Object> params, String key, Object defaultValue) { if (params.containsKey(key)) { return params.get(key); } else { return defaultValue; } } }