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