org.forgerock.openidm.patch.Main.java Source code

Java tutorial

Introduction

Here is the source code for org.forgerock.openidm.patch.Main.java

Source

/*
 * 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;
        }
    }
}