com.archivas.clienttools.arcmover.cli.ArcProfileMgr.java Source code

Java tutorial

Introduction

Here is the source code for com.archivas.clienttools.arcmover.cli.ArcProfileMgr.java

Source

// Copyright 2007 Hitachi Data Systems
// 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.archivas.clienttools.arcmover.cli;

import com.archivas.clienttools.arcutils.api.ArcMoverEngine;
import com.archivas.clienttools.arcutils.api.ArcMoverFactory;
import com.archivas.clienttools.arcutils.config.ConfigurationException;
import com.archivas.clienttools.arcutils.config.ConfigurationHelper;
import com.archivas.clienttools.arcutils.impl.adapter.ConnectionTestException;
import com.archivas.clienttools.arcutils.profile.*;
import com.archivas.clienttools.arcutils.utils.StringUtils;

import com.archivas.clienttools.arcutils.utils.database.DBUtils;
import org.apache.commons.cli.*;
import org.apache.commons.lang.text.StrBuilder;

import java.io.PrintWriter;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ArcProfileMgr extends AbstractArcCli {
    public static final String PACKAGE_NAME = ArcProfileMgr.class.getPackage().getName();
    public static final String CLASS_FULL_NAME = ArcProfileMgr.class.getName();
    public static final String CLASS_NAME = CLASS_FULL_NAME.substring(PACKAGE_NAME.length() + 1);
    public static Logger LOG = Logger.getLogger(CLASS_FULL_NAME);

    // Help Constants
    private static String HELP_USAGE_LINE;
    private static final String HELP_HEADER = "Creates, deletes, or displays information about namespace profiles.\n";
    private static final String HELP_FOOTER = "";

    /** Command Line Options **/
    private static Options cliOptions;

    // Program Options
    boolean printHelp = false;
    boolean listProfiles = false;
    boolean printProfile = false;
    boolean createProfile = false;
    boolean deleteProfile = false;

    String profileName = null;

    ProfileType newProfileType = null;
    String newProfileHostname = null;
    boolean dontTest;
    boolean newProfileRequireSSL = false;
    String newProfileTenant = null;
    String newProfileNamespace = null;
    String newProfileUsername = null;
    String newProfilePassword = null;
    List<String> newProfileIPList = null;
    boolean newProfileUseIPs = false;
    Integer newProfilePort = null;
    boolean authAnon = false;
    boolean validateCustomMetadata = true;

    // Command Data
    private Map<String, Integer> columnWidths;
    public static final String COL_SEP = "  ";
    public static final String KEY_PROFILE_NAME = "Name";
    public static final String KEY_PROFILE_ID = "ID";
    public static final String KEY_TYPE = "Type";
    public static final String KEY_HOSTNAME = "Host Name";
    public static final String KEY_TENANT = "Tenant";
    public static final String KEY_NAMESPACE = "Namespace";
    public static final String KEY_USERNAME = "User Name";
    public static final String KEY_SSL = "SSL Enabled";

    public static final boolean ALLOW_PORT = Boolean.getBoolean("portChoice");

    static {
        cliOrder.put("help", 0);
        cliOrder.put("list", 1);
        cliOrder.put("delete", 2);
        cliOrder.put("create", 3);
        cliOrder.put("type", 4);
        cliOrder.put("hostname", 5);
        cliOrder.put("ips", 6);
        cliOrder.put("tenant", 7);
        cliOrder.put("namespace", 8);
        cliOrder.put("username", 9);
        cliOrder.put("password", 10);
        cliOrder.put("anon", 11);
        cliOrder.put("ssl", 12);
        cliOrder.put("check-cm", 13);
        cliOrder.put("notest", 14);
        if (ALLOW_PORT) {
            cliOrder.put("port", 15);
        }
    }

    public ArcProfileMgr(String args[]) {
        super(args);
        // Always allow insecure so we can test the profile
        ProfileManager.initialize(new AbstractArcCli.ArcCliSSLCertificateCallback(true));

        HELP_USAGE_LINE = commandName
                + " profile -c <profile_name> | -d <profile_name> | -l [profile_name] [options]";
    }

    @SuppressWarnings({ "static-access" })
    public Options getOptions() {
        if (cliOptions == null) {
            Options options = new Options();

            OptionGroup operations = new OptionGroup();

            // *** Adding a new option needs to be added to the cliOrder list
            operations.addOption(OptionBuilder.withDescription("Displays this help text (the default behavior).")
                    .withLongOpt("help").create("h"));

            operations.addOption(OptionBuilder.hasOptionalArg().withArgName("profile_name").withDescription(
                    "Lists information about the specified namespace profile.  If <profile_name> is omitted, lists information about all namespace profiles.")
                    .withLongOpt("list").create("l"));
            operations.addOption(OptionBuilder.hasArg().withArgName("profile_name")
                    .withDescription("Deletes the specified namespace profile.").withLongOpt("delete").create("d"));
            operations.addOption(OptionBuilder.hasArg().withArgName("profile_name")
                    .withDescription("Creates a namespace profile with the specified name.").withLongOpt("create")
                    .create("c"));

            options.addOptionGroup(operations);

            // List the valid profile types dynamically
            String profileTypesToString = "Type of namespace for which you are creating the namespace profile: ";
            for (ProfileType type : ProfileType.values()) {
                if (type != ProfileType.FILESYSTEM) { // Filesystem is already createdcd
                    profileTypesToString += (type.toString() + " | ");
                }
            }
            profileTypesToString = profileTypesToString.substring(0, profileTypesToString.length() - 3);

            options.addOption(OptionBuilder.withArgName("profile_type").hasArg()
                    .withDescription(profileTypesToString).withLongOpt("type").create());
            options.addOption(OptionBuilder.withArgName("tenant_name").hasArg().withDescription(
                    "Name of the tenant that owns the namespace for which you are creating the namespace profile.")
                    .withLongOpt("tenant").create());
            options.addOption(OptionBuilder.withArgName("namespace_name").hasArg()
                    .withDescription("Name of the namespace for which you are creating the namespace profile.")
                    .withLongOpt("namespace").create());
            options.addOption(OptionBuilder.withArgName("username").hasArg()
                    .withDescription("Username of the data access account to use for namespace access.")
                    .withLongOpt("username").create());
            options.addOption(OptionBuilder.withArgName("password").hasArg()
                    .withDescription("Password of the data access account to use for namespace access.")
                    .withLongOpt("password").create());
            options.addOption(OptionBuilder.withArgName("hostname").hasArg()
                    .withDescription("The fully qualified domain name of the HCP system.").withLongOpt("hostname")
                    .create());
            options.addOption(
                    OptionBuilder.withDescription("Tells HCP-DM to use SSL when communicating with the HCP system.")
                            .withLongOpt("ssl").create());
            options.addOption(OptionBuilder.withDescription(
                    "Tells HCP-DM to check whether custom metadata XML is well-formed prior to communicating with the HCP system.")
                    .withLongOpt("check-cm").create());
            options.addOption(OptionBuilder
                    .withDescription("Log into namespace anonymously.  Only valid with HCP 5.0 or later profile.")
                    .withLongOpt("anon").create());
            options.addOption(OptionBuilder.withDescription(
                    "Does not test if the namespace configuration provided can access HCP while creating the profile.")
                    .withLongOpt("notest").create());
            options.addOption(OptionBuilder.withArgName("address1[,address2]...").hasArg().withDescription(
                    "Comma-separated list of one or more IP addresses to use to connect to the HCP system.  If omitted, HCP-DM uses the hostname.")
                    .withLongOpt("ips").create());
            if (ALLOW_PORT) {
                options.addOption(OptionBuilder.withArgName("port").hasArg()
                        .withDescription("Port to connect to HCP on").withLongOpt("port").create());
            }

            cliOptions = options;
        }
        return cliOptions;
    }

    public String getHelpFooter() {
        return HELP_FOOTER;
    }

    public String getHelpHeader() {
        return HELP_HEADER;
    }

    public String getHelpUsageLine() {
        return HELP_USAGE_LINE;
    }

    @SuppressWarnings({ "UseOfSystemOutOrSystemErr" })
    public static void main(String args[]) {
        ArcProfileMgr arcProfileMgr = null;

        ConfigurationHelper.validateLaunchOK();

        try {
            arcProfileMgr = new ArcProfileMgr(args);
            arcProfileMgr.parseArgs();
            if (arcProfileMgr.printHelp) {
                System.out.println(arcProfileMgr.helpScreen());
            } else {
                arcProfileMgr.execute(new PrintWriter(System.out), new PrintWriter(System.err));
            }
        } catch (ParseException e) {
            System.out.println("Error: " + e.getMessage());
            System.out.println();
            System.out.println(arcProfileMgr.helpScreen());
            arcProfileMgr.setExitCode(EXIT_CODE_OPTION_PARSE_ERROR);
        } catch (Exception e) {
            LOG.log(Level.SEVERE, "Unexpected Exception.", e);
            System.out.println();
            System.out.println("Failed to create a new profile " + e.getMessage());
            arcProfileMgr.setExitCode(EXIT_CODE_DM_ERROR);
        } finally {
            if (arcProfileMgr != null) {
                arcProfileMgr.exit();
            }
        }
    }

    @SuppressWarnings({ "UnusedCatchParameter" })
    protected void parseArgs() throws ParseException {

        // create the command cmdLine parser
        CommandLineParser parser = new PosixParser();
        CommandLine cmdLine;

        // parse the command cmdLine arguments
        cmdLine = parser.parse(getOptions(), getArgs());
        List<String> argList = cmdLine.getArgList();

        printHelp = cmdLine.hasOption("h");
        if (printHelp) {
            return;
        }

        listProfiles = cmdLine.hasOption("l");
        printProfile = cmdLine.hasOption("p");
        createProfile = cmdLine.hasOption("c");
        deleteProfile = cmdLine.hasOption("d");

        // Make sure only one of these is set
        if ((listProfiles && printProfile) || (listProfiles && createProfile) || (listProfiles && createProfile)
                || (printProfile && createProfile) || (printProfile && deleteProfile)
                || (createProfile && deleteProfile)) {
            throw new ParseException(" You may not specify more than one '-lpcd' option");
        }

        // Do validation
        if (printProfile) {
            profileName = getProfileNameFromCmdLineAndValidateExistance(cmdLine, "p");
        }

        if (createProfile) {
            profileName = getProfileNameAndValidateItDoesNotExist(cmdLine, "c");
            newProfileType = validateProfileType(cmdLine);
            dontTest = cmdLine.hasOption("notest");

            authAnon = cmdLine.hasOption("anon");

            validateCustomMetadata = cmdLine.hasOption("check-cm");

            newProfileIPList = validateIPs(cmdLine);
            newProfileUseIPs = newProfileIPList != null && newProfileIPList.size() > 0;

            if (newProfileType.equals(HCAPProfile.HCAP_TYPE)
                    || newProfileType.equals(Hcp3DefaultNamespaceProfile.HCAP_TYPE)) {
                // Default Namespace
                if (!newProfileUseIPs) {
                    // Only use hostname when not using IPs
                    newProfileHostname = validateHostname(cmdLine);
                }
                newProfileRequireSSL = validateRequireSSL(cmdLine);

                if (authAnon) {
                    throw new ParseException("Anonymous access only allowed with HCP 5.0 or later profile type.");
                }
            } else if (newProfileType.equals(Hcp3AuthNamespaceProfile.HCAP_TYPE)
                    || newProfileType.equals(Hcp5AuthNamespaceProfile.HCAP_TYPE)
                    || newProfileType.equals(Hcp6AuthNamespaceProfile.HCAP_TYPE)) {
                // Authenticated Namespace
                newProfileHostname = validateHostname(cmdLine);
                newProfileRequireSSL = validateRequireSSL(cmdLine);

                newProfileTenant = validateTenant(cmdLine);
                newProfileNamespace = validateNamespace(cmdLine);

                if (authAnon) {
                    if (cmdLine.hasOption("username") || cmdLine.hasOption("password")) {
                        throw new ParseException("Cannot specify ANON and a username or password!");
                    }
                } else {
                    newProfileUsername = validateUsername(cmdLine);
                    newProfilePassword = validatePassword(cmdLine);
                }
            }
            if (ALLOW_PORT) {
                newProfilePort = validatePort(cmdLine);
            }
        }

        if (deleteProfile) {
            profileName = getProfileNameFromCmdLineAndValidateExistance(cmdLine, "d");
            if (profileName.equalsIgnoreCase(LFS)
                    || profileName.equalsIgnoreCase(FileSystemProfile.DEFAULT_FILESYSTEM_PROFILE_NAME)) {
                throw new ParseException("Cannot delete the default file system profile.");
            }
        }

        if (listProfiles) {
            // If there is a profile name we are just printing info about that profile
            if (argList.size() > 1) {
                throw new ParseException("-l takes 0 or 1 arguments");
            } else if (argList.size() == 1) {
                String namePassedIn = cmdLine.getOptionValue('l');
                if (namePassedIn != null) {
                    listProfiles = false;
                    printProfile = true;
                    profileName = validateProfileNameExists(namePassedIn);
                }
            }
        }
    }

    public ProfileType validateProfileType(CommandLine cmdLine) throws ParseException {
        String option = "type";
        if (!cmdLine.hasOption(option)) {
            throw new ParseException("Missing Option: " + option);
        }

        String value = cmdLine.getOptionValue(option);
        ProfileType retval = null;

        boolean validType = false;
        for (ProfileType type : ProfileType.values()) {
            if (type.toString().equals(value) && type != ProfileType.FILESYSTEM) { // Filesystem is
                                                                                   // already
                                                                                   // created
                validType = true;
            }
        }

        if (!validType) {
            throw new ParseException("Invalid Profile Type: " + value);
        }

        retval = ProfileType.valueOf(value);
        if (retval == null) {
            throw new ParseException("Invalid Profile Type: " + newProfileType);
        }

        return retval;
    }

    public List<String> validateIPs(CommandLine cmdLine) throws ParseException {
        List<String> ipList = null;
        String option = "ips";
        if (cmdLine.hasOption(option)) {
            String value = cmdLine.getOptionValue(option);
            String[] ipArray = org.apache.commons.lang.StringUtils.split(value, ",");

            // Convert since create profile wants a list
            if (ipArray != null) {
                ipList = new ArrayList<String>();
                for (int i = 0; i < ipArray.length; ++i) {
                    ipList.add(ipArray[i]);
                }
            }
        }

        return ipList;
    }

    public String validateHostname(CommandLine cmdLine) throws ParseException {
        String option = "hostname";
        if (!cmdLine.hasOption(option)) {
            throw new ParseException("Missing Option: " + option);
        }

        String value = cmdLine.getOptionValue(option);
        String retval = null;

        if (value != null) {
            retval = value;
        }
        if (retval == null) {
            throw new ParseException("Invalid " + option + " : " + value);
        }
        return retval;
    }

    public boolean validateRequireSSL(CommandLine cmdLine) throws ParseException {
        String option = "ssl";
        return cmdLine.hasOption(option);
    }

    public Integer validatePort(CommandLine cmdLine) throws ParseException {
        String option = "port";

        if (!cmdLine.hasOption(option)) {
            return null;
        }

        Integer port = null;
        String value = cmdLine.getOptionValue(option);
        if (value != null) {
            try {
                port = Integer.valueOf(value);
                if (port < 0) {
                    throw new NumberFormatException();
                }
            } catch (NumberFormatException nfe) {
                throw new ParseException("Invalid " + option + " : " + value);
            }
        }
        return port;
    }

    public String validateTenant(CommandLine cmdLine) throws ParseException {
        String option = "tenant";
        if (!cmdLine.hasOption(option)) {
            throw new ParseException("Missing Option: " + option);
        }

        String value = cmdLine.getOptionValue(option);
        String retval = null;

        if (value != null) {
            retval = value;
        }
        if (retval == null) {
            throw new ParseException("Invalid " + option + " : " + value);
        }
        return retval;
    }

    public String validateNamespace(CommandLine cmdLine) throws ParseException {
        String option = "namespace";
        if (!cmdLine.hasOption(option)) {
            throw new ParseException("Missing Option: " + option);
        }

        String value = cmdLine.getOptionValue(option);
        String retval = null;

        if (value != null) {
            retval = value;
        }
        if (retval == null) {
            throw new ParseException("");
        }
        return retval;
    }

    public String validateUsername(CommandLine cmdLine) throws ParseException {
        String option = "username";
        if (!cmdLine.hasOption(option)) {
            throw new ParseException("Missing Option: " + option);
        }

        String value = cmdLine.getOptionValue(option);
        String retval = null;

        if (value != null) {
            retval = value;
        }
        if (retval == null) {
            throw new ParseException("Invalid " + option + " : " + value);
        }
        return retval;
    }

    public String validatePassword(CommandLine cmdLine) throws ParseException {
        String option = "password";
        if (!cmdLine.hasOption(option)) {
            throw new ParseException("Missing Option: " + option);
        }

        String value = cmdLine.getOptionValue(option);
        String retval = null;

        if (value != null) {
            retval = Hcp3AuthNamespaceProfile.getMD5Password(value.toCharArray());
        }
        if (retval == null) {
            throw new ParseException("Invalid " + option + " : " + value);
        }
        return retval;
    }

    /**
     * Entry point for generating a directory listing.
     */
    public void execute(PrintWriter out, PrintWriter err) {
        StrBuilder output = new StrBuilder();

        if (listProfiles) {
            output.append(createProfileListing());
        } else if (printProfile) {
            output.append(getProfileDetails(profileName));
        } else if (createProfile) {
            output.append(createProfile());
        } else if (deleteProfile) {
            output.append(deleteProfile());
        } else {
            output.append(helpScreen());
        }

        out.println(output.toString());
        out.flush();
        err.flush();
    }

    /**
     * Helper Method to load the profile list.
     */
    protected List<AbstractProfileBase> getProfiles() {
        List<AbstractProfileBase> profileList = ProfileManager.getProfiles();
        profileList.add(0, FileSystemProfile.LOCAL_FILESYSTEM_PROFILE);
        return profileList;
    }

    /**
     * Delete an existing Profile
     */
    protected String deleteProfile() {
        StrBuilder output = new StrBuilder();
        AbstractProfileBase profile = ProfileManager.getProfileByName(profileName);

        try {
            ProfileManager.deleteProfile(profile, true);
        } catch (Exception e) {
            String errMsg = "Error deleting profile '" + profile + "'";
            errMsg = DBUtils.getErrorMessage(errMsg, e);
            LOG.log(Level.WARNING, errMsg, e);
            output.append(errMsg);
            setExitCode(EXIT_CODE_DM_ERROR);
        }

        return output.toString();
    }

    /**
     * Create a new Profile.
     * 
     * @return
     */
    protected String createProfile() {
        StrBuilder output = new StrBuilder();
        AbstractProfileBase profile = ProfileManager.getInstance().createProfile(newProfileType);

        profile.setName(profileName);
        if (profile instanceof HCAPProfile) {
            HCAPProfile fcfsProfile = (HCAPProfile) profile;
            fcfsProfile.setSSLRequired(newProfileRequireSSL);
            fcfsProfile.setValidateCustomMetadata(validateCustomMetadata);
            if (newProfileUseIPs) {
                fcfsProfile.setIpAddressList(newProfileIPList);
                fcfsProfile.setConnectByDns(false);
            } else {
                // Only set hostname if not using IPs
                fcfsProfile.setHostname(newProfileHostname);
            }
            fcfsProfile.setPort(newProfilePort);
        }
        if ((profile instanceof Hcp3AuthNamespaceProfile) || (profile instanceof Hcp5AuthNamespaceProfile)) {
            Hcp3AuthNamespaceProfile restProfile = (Hcp3AuthNamespaceProfile) profile;
            restProfile.setHostname(newProfileHostname); // Always set on HCP3Auth no matter what
            restProfile.setTenant(newProfileTenant);
            restProfile.setNamespace(newProfileNamespace);

            if (!restProfile.getType().supportsAnonymousAccess() && authAnon) {
                String errMsg = "Profile does not support anonymous access.";
                LOG.log(Level.WARNING, errMsg, "errMsg");
                output.append(errMsg);
                setExitCode(EXIT_CODE_DM_ERROR);
            }

            if (restProfile.getType().supportsAnonymousAccess() && authAnon) {
                Hcp5AuthNamespaceProfile hcp5profile = (Hcp5AuthNamespaceProfile) restProfile;
                hcp5profile.setAnonymousAccess(true);
            } else {
                restProfile.setUsername(newProfileUsername);
                restProfile.setPassword(newProfilePassword);
            }
        }

        String badConnectionErrMsg = "Error connecting using the values passed in";
        try {
            boolean canConnect = true;
            if (!dontTest) {
                ArcMoverEngine arcMover = ArcMoverFactory.getInstance();
                canConnect = arcMover.testConnection(profile);
            }

            if (canConnect) {
                ProfileManager.saveProfile(profile);
            } else {
                output.append(badConnectionErrMsg);
            }

        } catch (ConfigurationException e) {
            String errMsg = "Error updating/saving new Profile: " + profile;
            LOG.log(Level.WARNING, errMsg, e);
            output.append(errMsg);
            setExitCode(EXIT_CODE_DM_ERROR);
        } catch (ConnectionTestException e) {
            LOG.log(Level.WARNING, badConnectionErrMsg, e);
            output.append(badConnectionErrMsg + ": " + e.getMessage());
            setExitCode(EXIT_CODE_DM_ERROR);
        }

        return output.toString();
    }

    /**
     * get details for the profile specified.
     * 
     * @return
     */
    protected String getProfileDetails(String profileName) {
        StrBuilder output = new StrBuilder();
        AbstractProfileBase profile = ProfileManager.getProfileByName(profileName);
        if (profile == null) {
            output.append("Profile does not exist: ").append(profileName);
            output.append(NEWLINE);
            output.append(helpScreen());
        } else {
            output.append("Name        : ").append(profile.getName()).append(" ").append(NEWLINE);
            output.append("Type        : ").append(profile.getType()).append(" ").append(NEWLINE);
            if (profile instanceof HCAPProfile) {
                HCAPProfile fcfsProfile = (HCAPProfile) profile;

                // Connect-by-DNS and Authenticated namespaces have a domain name to display
                if (fcfsProfile.isConnectByDns() || fcfsProfile.isAuthNamespace()) {
                    output.append("Domain Name : ").append(fcfsProfile.getHostname()).append(" ").append(NEWLINE);
                }

                if (!fcfsProfile.isConnectByDns()) {
                    output.append("IP List     : ").append(StringUtils.createCSV(fcfsProfile.getIpAddressList()))
                            .append(" ").append(NEWLINE);
                }

                output.append("SSL         : ");
                if (fcfsProfile.isSSLRequired()) {
                    output.append("Enabled");
                } else {
                    output.append("Disabled");
                }
                output.append(" ").append(NEWLINE);

                output.append("Validate CM : ");
                if (fcfsProfile.isValidateCustomMetadata()) {
                    output.append("Enabled");
                } else {
                    output.append("Disabled");
                }
                output.append(" ").append(NEWLINE);
                if (ALLOW_PORT) {
                    output.append("Port        : ").append(fcfsProfile.getPort()).append(" ").append(NEWLINE);
                }
            }
            if (profile instanceof Hcp3AuthNamespaceProfile) {
                Hcp3AuthNamespaceProfile restProfile = (Hcp3AuthNamespaceProfile) profile;
                output.append("Tenant      : ").append(restProfile.getTenant()).append(" ").append(NEWLINE);
                output.append("Namespace   : ").append(restProfile.getNamespace()).append(" ").append(NEWLINE);
                output.append("User Name   : ").append(restProfile.getUsername()).append(" ").append(NEWLINE);
            }
        }
        return output.toString();
    }

    /**
     * Create the Profile listing output
     */
    protected String createProfileListing() {
        StrBuilder sb = new StrBuilder();
        List<AbstractProfileBase> profileList = getProfiles();

        columnWidths = calcColumnWidths(profileList);

        for (Map.Entry<String, Integer> entry : columnWidths.entrySet()) {
            sb.appendFixedWidthPadRight(entry.getKey(), entry.getValue(), ' ').append(COL_SEP);
        }
        sb.append(NEWLINE);

        for (AbstractProfileBase profile : profileList) {
            sb.appendFixedWidthPadRight("" + profile.getName(), columnWidths.get(KEY_PROFILE_NAME), ' ')
                    .append(COL_SEP);
            sb.appendFixedWidthPadRight("" + profile.getType(), columnWidths.get(KEY_TYPE), ' ').append(COL_SEP);
            sb.append(NEWLINE);
        }

        return sb.toString();
    }

    /**
     * Crude mechanism to calculate column widths.
     *
     * TODO: This could be made more efficient.
     */
    protected Map<String, Integer> calcColumnWidths(List<AbstractProfileBase> profileList) {
        Map<String, Integer> columnWidths = new LinkedHashMap<String, Integer>();
        columnWidths.put(KEY_PROFILE_NAME, KEY_PROFILE_NAME.length());
        columnWidths.put(KEY_TYPE, KEY_TYPE.length());
        String key = null;
        for (AbstractProfileBase profile : profileList) {
            key = KEY_PROFILE_NAME;
            columnWidths.put(key, Math.max(columnWidths.get(key), ("" + profile.getName()).length()));
            key = KEY_TYPE;
            columnWidths.put(key, Math.max(columnWidths.get(key), ("" + profile.getType()).length()));
        }
        return columnWidths;
    }

}