com.pentaho.di.purge.RepositoryCleanupUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.pentaho.di.purge.RepositoryCleanupUtil.java

Source

/*!
 * Copyright 2010 - 2015 Pentaho Corporation.  All rights reserved.
 *
 * Licensed 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 com.pentaho.di.purge;

import java.io.File;
import java.io.FileOutputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.core.MediaType;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.pentaho.platform.security.policy.rolebased.actions.AdministerSecurityAction;
import org.pentaho.platform.util.RepositoryPathEncoder;

import com.pentaho.di.messages.Messages;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.multipart.FormDataMultiPart;

/**
 * Command line utility for purging files, file revisions, or shared objects. Passes through to purge services.
 */
public class RepositoryCleanupUtil {

    public static Client client; // public so can be injected by unit test
    public static boolean testMode; // system.exit() is disabled for unit tests

    // Utility parameters.
    private final String URL = "url";
    private final String USER = "user";
    private final String PASS = "password";
    private final String VER_COUNT = "versionCount";
    private final String DEL_DATE = "purgeBeforeDate";
    private final String PURGE_FILES = "purgeFiles";
    private final String PURGE_REV = "purgeRevisions";

    private final String LOG_LEVEL = "logLevel";
    private final String LOG_FILE = "logFileName";
    private final String PURGE_SHARED = "purgeSharedObjects";

    // parameters in rest call that are not in command line
    private final String FILE_FILTER = "fileFilter";

    // Constants.
    private final String SERVICE_NAME = "purge";
    private final String BASE_PATH = "/plugin/pur-repository-plugin/api/purge";
    private final String AUTHENTICATION = "/api/authorization/action/isauthorized?authAction=";
    private final String purgeBeforeDateFormat = "MM/dd/yyyy";
    private final String logFileNameDateFormat = "YYYYMMdd-HHmmss";
    private final String DEFAULT_LOG_FILE_PREFIX = "purge-utility-log-";
    private final String OPTION_PREFIX = "-";
    private final String NEW_LINE = "\n";

    // Class properties.
    private String url = null;
    private String username = null;
    private String password = null;
    private int verCount = -1;
    private String delFrom = null;
    private String logLevel = null;
    private boolean purgeFiles = false;
    private boolean purgeRev = false;
    private boolean purgeShared = false;
    private String logFile = null;
    private String fileFilter;
    private String repositoryPath;

    /**
     * Main method
     * 
     * @param args
     */
    public static void main(String[] args) {
        try {
            new RepositoryCleanupUtil().purge(args);
        } catch (Exception e) {
            writeOut(e);
        }
        exit(0);
    }

    /**
     * Create parameters and send HTTP request to purge REST endpoint
     * 
     * @param options
     */
    public void purge(String[] options) {
        FormDataMultiPart form = null;
        try {
            Map<String, String> parameters = parseExecutionOptions(options);
            validateParameters(parameters);
            authenticateLoginCredentials();

            String serviceURL = createPurgeServiceURL();
            form = createParametersForm();

            WebResource resource = client.resource(serviceURL);
            ClientResponse response = resource.type(MediaType.MULTIPART_FORM_DATA).post(ClientResponse.class, form);

            if (response != null && response.getStatus() == 200) {
                String resultLog = response.getEntity(String.class);
                String logName = writeLog(resultLog);
                writeOut(Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0001.OP_SUCCESS", logName),
                        false);
            } else {
                writeOut(Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.ERROR_0001.OP_FAILURE"), true);
            }

        } catch (Exception e) {
            if (e.getMessage() != null) {
                System.out.println(e.getMessage());
            } else {
                if (!(e instanceof NormalExitException)) {
                    e.printStackTrace();
                }
            }
        } finally {
            if (client != null) {
                client.destroy();
            }

            if (form != null) {
                form.cleanup();
            }
        }
    }

    /**
     * Attempt to parse command line arguments and create map of values
     * 
     * @param args
     * @return
     * @throws Exception
     */
    private Map<String, String> parseExecutionOptions(String[] args) throws Exception {
        Map<String, String> arguments = new HashMap<String, String>();
        String param;
        String value;
        try {
            for (String arg : args) {
                int equalsPos = arg.indexOf("=");
                if (equalsPos == -1) {
                    param = arg;
                    value = "true";
                } else {
                    param = arg.substring(0, equalsPos);
                    value = arg.substring(equalsPos + 1, arg.length());
                }
                arguments.put(param, value);
            }
        } catch (Exception e) {
            writeOut(Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.ERROR_0002.ERROR_PROC_PARAMS"),
                    true);
        }
        if (arguments.size() == 0) {
            writeOut(printHelp(), true);
        }
        return arguments;
    }

    /**
     * Ensure provided parameters are present and what we expect
     * 
     * @param arguments
     * @throws Exception
     */
    private void validateParameters(Map<String, String> arguments) throws Exception {
        String aUrl = arguments.get(OPTION_PREFIX + URL);
        String aUser = arguments.get(OPTION_PREFIX + USER);
        String aPassword = arguments.get(OPTION_PREFIX + PASS);
        String aVerCount = arguments.get(OPTION_PREFIX + VER_COUNT);
        String aDelFrom = arguments.get(OPTION_PREFIX + DEL_DATE);
        String aPurgeFiles = arguments.get(OPTION_PREFIX + PURGE_FILES);
        String aPurgeRev = arguments.get(OPTION_PREFIX + PURGE_REV);
        String aPurgeShared = arguments.get(OPTION_PREFIX + PURGE_SHARED);

        String aLogLevel = arguments.get(OPTION_PREFIX + LOG_LEVEL);
        String aLogFile = arguments.get(OPTION_PREFIX + LOG_FILE);

        StringBuffer errors = new StringBuffer();

        boolean isValidOperationSelected = false;

        fileFilter = "*.kjb|*.ktr";
        repositoryPath = "/";
        purgeShared = false;

        if (aLogLevel != null && !(aLogLevel.equals("DEBUG") || aLogLevel.equals("ERROR")
                || aLogLevel.equals("FATAL") || aLogLevel.equals("INFO") || aLogLevel.equals("OFF")
                || aLogLevel.equals("TRACE") || aLogLevel.equals("WARN"))) {
            errors.append(OPTION_PREFIX + LOG_LEVEL + "=" + aLogLevel + " "
                    + Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.ERROR_0003.INVALID_LOGLEVEL")
                    + "\n");
        } else {
            logLevel = aLogLevel;
        }

        if (aLogFile != null) {
            File f = new File(aLogFile);
            if (f.exists() && f.isDirectory()) {
                errors.append(Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.ERROR_0004.FOLDER_EXISTS",
                        OPTION_PREFIX + LOG_FILE) + "\n");
            }
            logFile = aLogFile;
        }

        if (aUrl == null) {
            errors.append(Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.ERROR_0005.MISSING_PARAM",
                    OPTION_PREFIX + URL) + "\n");
        } else {
            url = aUrl;
        }

        if (aUser == null) {
            errors.append(Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.ERROR_0005.MISSING_PARAM",
                    OPTION_PREFIX + USER) + "\n");
        } else {
            username = aUser;
        }

        if (aPassword == null) {
            errors.append(Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.ERROR_0005.MISSING_PARAM",
                    OPTION_PREFIX + PASS) + "\n");
        } else {
            password = aPassword;
        }

        if (aPurgeFiles != null) {
            if ((aPurgeFiles.equalsIgnoreCase(Boolean.TRUE.toString())
                    || aPurgeFiles.equalsIgnoreCase(Boolean.FALSE.toString()))) {
                purgeFiles = Boolean.parseBoolean(aPurgeFiles);
                isValidOperationSelected = true;
            } else {
                errors.append(Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.ERROR_0006.INVALID_BOOLEAN",
                        OPTION_PREFIX + PURGE_FILES + "=" + aPurgeFiles) + "\n");
            }
        }

        if (aPurgeRev != null) {
            if (aPurgeRev.equalsIgnoreCase(Boolean.TRUE.toString())
                    || aPurgeRev.equalsIgnoreCase(Boolean.FALSE.toString())) {
                if (isValidOperationSelected) {
                    errors.append(Messages.getInstance()
                            .getString("REPOSITORY_CLEANUP_UTIL.ERROR_0010.INVALID_COMBINATION_OF_PARAMS") + "\n");
                } else {
                    purgeRev = Boolean.parseBoolean(aPurgeRev);
                    isValidOperationSelected = true;
                }
            } else {
                errors.append(Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.ERROR_0006.INVALID_BOOLEAN",
                        OPTION_PREFIX + PURGE_REV + "=" + aPurgeRev) + "\n");
            }
        }

        if (aPurgeShared != null) {
            if (Boolean.parseBoolean(aPurgeFiles) != Boolean.TRUE) {
                errors.append(Messages.getInstance()
                        .getString("REPOSITORY_CLEANUP_UTIL.ERROR_0014.INVALID_PURGE_SHARED"));
            } else {
                purgeShared = Boolean.parseBoolean(aPurgeShared);
            }
        }

        if (aDelFrom != null) {
            // only allow one operation
            if (isValidOperationSelected) {
                errors.append(Messages.getInstance()
                        .getString("REPOSITORY_CLEANUP_UTIL.ERROR_0010.INVALID_COMBINATION_OF_PARAMS") + "\n");
            } else {
                SimpleDateFormat sdf = new SimpleDateFormat(purgeBeforeDateFormat);
                sdf.setLenient(false);
                try {
                    sdf.parse(aDelFrom);
                    delFrom = aDelFrom;
                    isValidOperationSelected = true;
                } catch (ParseException e) {
                    errors.append(Messages.getInstance().getString(
                            "REPOSITORY_CLEANUP_UTIL.ERROR_0008.IMPROPERLY_FORMATTED_DATE",
                            OPTION_PREFIX + DEL_DATE + "=" + aDelFrom, purgeBeforeDateFormat) + "\n");
                }
            }
        }

        if (aVerCount != null) {
            // only allow one operation
            if (isValidOperationSelected) {
                errors.append(Messages.getInstance()
                        .getString("REPOSITORY_CLEANUP_UTIL.ERROR_0010.INVALID_COMBINATION_OF_PARAMS") + "\n");
            } else {
                try {
                    verCount = Integer.parseInt(aVerCount);
                    isValidOperationSelected = true;
                } catch (NumberFormatException e) {
                    errors.append(
                            Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.ERROR_0009.INVALID_INTEGER",
                                    OPTION_PREFIX + VER_COUNT + "=" + aVerCount) + "\n");
                }
            }
        }

        if (!isValidOperationSelected) {
            errors.append(Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.ERROR_0013.MISSING_OPERATION")
                    + "\n");
        }

        if (errors.length() != 0) {
            errors.insert(0, "\n\n"
                    + Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.ERROR_0011.ERRORS_HEADER") + "\n");
            throw new Exception(errors.toString());
        }
    }

    /**
     * Use REST API to authenticate provided credentials
     * 
     * @throws Exception
     */
    private void authenticateLoginCredentials() throws Exception {

        if (client == null) {
            ClientConfig clientConfig = new DefaultClientConfig();
            clientConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
            client = Client.create(clientConfig);
            client.addFilter(new HTTPBasicAuthFilter(username, password));
        }

        WebResource resource = client.resource(url + AUTHENTICATION + AdministerSecurityAction.NAME);
        String response = resource.get(String.class);

        if (!response.equals("true")) {
            throw new Exception(
                    Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.ERROR_0012.ACCESS_DENIED"));
        }
    }

    /**
     * Create URL to access REST API based on provided parameters
     * 
     * @return
     * @throws Exception
     */
    private String createPurgeServiceURL() throws Exception {
        StringBuffer service = new StringBuffer();
        service.append(url);
        service.append(BASE_PATH);
        service.append("/");

        String path = RepositoryPathEncoder.encodeRepositoryPath(repositoryPath);
        path = RepositoryPathEncoder.encode(path);
        service.append(path + "/");

        service.append(SERVICE_NAME);
        return service.toString();
    }

    /**
     * Create payload to supply with POST to REST API
     * 
     * @return
     */
    private FormDataMultiPart createParametersForm() {
        FormDataMultiPart form = new FormDataMultiPart();
        if (verCount != -1 && !purgeRev) {
            form.field(VER_COUNT, Integer.toString(verCount), MediaType.MULTIPART_FORM_DATA_TYPE);
        }
        if (delFrom != null && !purgeRev) {
            form.field(DEL_DATE, delFrom, MediaType.MULTIPART_FORM_DATA_TYPE);
        }
        if (fileFilter != null) {
            form.field(FILE_FILTER, fileFilter, MediaType.MULTIPART_FORM_DATA_TYPE);
        }
        if (logLevel != null) {
            form.field(LOG_LEVEL, logLevel, MediaType.MULTIPART_FORM_DATA_TYPE);
        }
        if (purgeFiles) {
            form.field(PURGE_FILES, Boolean.toString(purgeFiles), MediaType.MULTIPART_FORM_DATA_TYPE);
        }
        if (purgeRev) {
            form.field(PURGE_REV, Boolean.toString(purgeRev), MediaType.MULTIPART_FORM_DATA_TYPE);
        }
        if (purgeShared) {
            form.field(PURGE_SHARED, Boolean.toString(purgeShared), MediaType.MULTIPART_FORM_DATA_TYPE);
        }
        return form;
    }

    /**
     * Generate help output
     * 
     * @return
     */
    private String printHelp() {

        // TODO improve this help description....
        StringBuffer help = new StringBuffer();

        help.append("\n\n" + Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0003.OPTIONS_HEADER"));

        help.append(optionHelp(URL,
                Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0004.URL_REQUIRED", URL)));

        help.append(optionHelp(USER,
                Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0005.USER_REQUIRED", USER)));

        help.append(optionHelp(PASS,
                Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0006.PASS_REQUIRED", PASS)));

        help.append("\n");

        help.append(indentFormat(
                Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0008.PARAMS_HELP", PURGE_SHARED), 0,
                0));

        help.append(optionHelp(VER_COUNT, Messages.getInstance()
                .getString("REPOSITORY_CLEANUP_UTIL.INFO_0009.VERSIONCOUNT_HELP", VER_COUNT)));

        help.append(optionHelp(DEL_DATE,
                Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0010.DATE_HELP", DEL_DATE)));

        help.append(optionHelp(PURGE_FILES, Messages.getInstance()
                .getString("REPOSITORY_CLEANUP_UTIL.INFO_0011.PURGE_FILES_HELP", PURGE_FILES)));

        help.append(optionHelp(PURGE_REV,
                Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0012.PURGE_REVS_HELP", PURGE_REV)));

        help.append("\n\n" + Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0013.OPTIONAL_PARAMS"));

        help.append(optionHelp(LOG_FILE,
                Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0014.LOGFILE_HELP",
                        DEFAULT_LOG_FILE_PREFIX + logFileNameDateFormat, logFileNameDateFormat)));

        help.append(optionHelp(LOG_LEVEL,
                Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0015.LOGLEVEL_HELP", LOG_LEVEL)));

        help.append(optionHelp(PURGE_SHARED,
                Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0007.PURGE_SHARED", PURGE_FILES)));

        help.append("\n\n" + Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0016.EXAMPLES"));

        help.append(indentFormat(Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0017.EXAMPLE_1",
                OPTION_PREFIX + URL, OPTION_PREFIX + USER, OPTION_PREFIX + PASS, OPTION_PREFIX + PURGE_FILES), 0,
                3));

        help.append(indentFormat(
                Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0018.EXAMPLE_1_DESC"), 3, 3));

        help.append(indentFormat(Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0019.EXAMPLE_2",
                OPTION_PREFIX + URL, OPTION_PREFIX + USER, OPTION_PREFIX + PASS, OPTION_PREFIX + PURGE_REV), 0, 3));

        help.append(indentFormat(
                Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0020.EXAMPLE_2_DESC"), 3, 3));

        help.append(indentFormat(Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0021.EXAMPLE_3",
                OPTION_PREFIX + URL, OPTION_PREFIX + USER, OPTION_PREFIX + PASS, OPTION_PREFIX + DEL_DATE), 0, 3));

        help.append(indentFormat(
                Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0022.EXAMPLE_3_DESC"), 3, 3));

        help.append(indentFormat(Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0023.EXAMPLE_4",
                OPTION_PREFIX + URL, OPTION_PREFIX + USER, OPTION_PREFIX + PASS, OPTION_PREFIX + PURGE_FILES,
                OPTION_PREFIX + PURGE_SHARED), 0, 3));

        help.append(indentFormat(
                Messages.getInstance().getString("REPOSITORY_CLEANUP_UTIL.INFO_0024.EXAMPLE_4_DESC"), 3, 3));

        return help.toString();
    }

    /**
     * Format string for option help
     * 
     * @param optionName
     * @param descriptionText
     * @return
     */
    private String optionHelp(String optionName, String descriptionText) {
        int indentFirstLine = 2;
        int indentBalance = Math.min(OPTION_PREFIX.length() + optionName.length() + 4, 10);
        return indentFormat(OPTION_PREFIX + optionName + ": " + descriptionText, indentFirstLine, indentBalance);
    }

    /**
     * Format strings for command line output
     * 
     * @param unformattedText
     * @param indentFirstLine
     * @param indentBalance
     * @return
     */
    private String indentFormat(String unformattedText, int indentFirstLine, int indentBalance) {
        final int maxWidth = 79;
        String leadLine = WordUtils.wrap(unformattedText, maxWidth - indentFirstLine);
        StringBuilder result = new StringBuilder();
        result.append("\n");
        if (leadLine.indexOf(NEW_LINE) == -1) {
            result.append(NEW_LINE).append(StringUtils.repeat(" ", indentFirstLine)).append(unformattedText);
        } else {
            int lineBreakPoint = leadLine.indexOf(NEW_LINE);
            String indentString = StringUtils.repeat(" ", indentBalance);
            result.append(NEW_LINE).append(StringUtils.repeat(" ", indentFirstLine))
                    .append(leadLine.substring(0, lineBreakPoint));
            String formattedText = WordUtils.wrap(unformattedText.substring(lineBreakPoint),
                    maxWidth - indentBalance);
            for (String line : formattedText.split(NEW_LINE)) {
                result.append(NEW_LINE).append(indentString).append(line);
            }
        }
        return result.toString();
    }

    /**
     * Write out to log file
     * 
     * @param message
     * @return
     * @throws Exception
     */
    private String writeLog(String message) throws Exception {
        String logName;
        if (logFile != null) {
            logName = logFile;
        } else {
            DateFormat df = new SimpleDateFormat(logFileNameDateFormat);
            logName = DEFAULT_LOG_FILE_PREFIX + df.format(new Date()) + ".txt";
        }
        File file = new File(logName);
        FileOutputStream fout = FileUtils.openOutputStream(file);
        IOUtils.copy(IOUtils.toInputStream(message), fout);
        fout.close();
        return logName;
    }

    /**
     * Print output or error message
     * 
     * @param message
     * @param isError
     */
    private static void writeOut(String message, boolean isError) {
        if (isError) {
            System.err.println(message);
            exit(1);
        } else {
            System.out.println(message);
        }
    }

    private static void exit(int exitCode) {
        if (!testMode) {
            System.exit(exitCode);
        } else {
            throw new NormalExitException(exitCode);
        }
    }

    /**
     * Print stack trace on error
     * 
     * @param t
     */
    private static void writeOut(Throwable t) {
        t.printStackTrace();
        exit(1);
    }

    /**
     * When the command line would normally exit, this exception is thrown instead if we are running in test mode. This
     * prevents the junit tests from abnormally terminating but still captures the exit code.
     * 
     * @author tkafalas
     * 
     */
    public static class NormalExitException extends RuntimeException {
        public int exitCode;

        public NormalExitException(int exitCode) {
            super();
            this.exitCode = exitCode;
        }
    }
}