com.atlassian.jira.startup.JiraSystemInfo.java Source code

Java tutorial

Introduction

Here is the source code for com.atlassian.jira.startup.JiraSystemInfo.java

Source

package com.atlassian.jira.startup;

import com.atlassian.core.ofbiz.util.OFBizPropertyUtils;
import com.atlassian.core.util.ClassLoaderUtils;
import com.atlassian.crowd.embedded.api.CrowdDirectoryService;
import com.atlassian.crowd.embedded.api.Directory;
import com.atlassian.jdk.utilities.runtimeinformation.MemoryInformation;
import com.atlassian.jira.ComponentManager;
import com.atlassian.jira.bc.license.JiraLicenseService;
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.config.properties.APKeys;
import com.atlassian.jira.config.properties.ApplicationProperties;
import com.atlassian.jira.config.properties.JiraProperties;
import com.atlassian.jira.config.properties.JiraSystemProperties;
import com.atlassian.jira.config.util.JiraHome;
import com.atlassian.jira.configurableobjects.ConfigurableObjectUtil;
import com.atlassian.jira.issue.attachment.AttachmentConstants;
import com.atlassian.jira.issue.index.IssueIndexManager;
import com.atlassian.jira.issue.index.SearchUnavailableException;
import com.atlassian.jira.license.LicenseDetails;
import com.atlassian.jira.ofbiz.DefaultOfBizConnectionFactory;
import com.atlassian.jira.ofbiz.OfBizConnectionFactory;
import com.atlassian.jira.ofbiz.OfBizDelegator;
import com.atlassian.jira.security.auth.trustedapps.TrustedApplicationInfo;
import com.atlassian.jira.security.auth.trustedapps.TrustedApplicationManager;
import com.atlassian.jira.security.auth.trustedapps.TrustedApplicationService;
import com.atlassian.jira.service.JiraService;
import com.atlassian.jira.service.JiraServiceContainer;
import com.atlassian.jira.service.ServiceManager;
import com.atlassian.jira.upgrade.UpgradeHistoryItem;
import com.atlassian.jira.upgrade.UpgradeManager;
import com.atlassian.jira.util.BuildUtilsInfo;
import com.atlassian.jira.util.JiraUtils;
import com.atlassian.jira.util.collect.CollectionBuilder;
import com.atlassian.jira.util.log.JiraLogLocator;
import com.atlassian.jira.util.system.ExtendedSystemInfoUtils;
import com.atlassian.jira.util.system.ExtendedSystemInfoUtilsImpl;
import com.atlassian.jira.util.system.ReleaseInfo;
import com.atlassian.jira.util.system.SystemInfoUtils;
import com.atlassian.jira.util.system.SystemInfoUtilsImpl;
import com.atlassian.jira.util.system.patch.AppliedPatchInfo;
import com.atlassian.jira.util.system.patch.AppliedPatches;
import com.atlassian.jira.web.bean.I18nBean;
import com.opensymphony.module.propertyset.PropertySet;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.ofbiz.core.entity.GenericValue;
import org.ofbiz.core.entity.config.DatasourceInfo;

import javax.servlet.ServletContext;
import java.io.File;
import java.net.URL;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Pattern;

import static com.atlassian.jira.util.dbc.Assertions.notNull;

/**
 * This will obtain JIRA system information and place it in the specified {@link com.atlassian.jira.startup.FormattedLogMsg}
 * <p/>
 * This is used at JIRA startup time and is VERY aware of when certain methods can be called and when they cant.  During
 * startup we only access certain JIRA code very carefully.
 *
 * @since v3.13
 */
public class JiraSystemInfo {
    private static final Logger log = Logger.getLogger(JiraSystemInfo.class);

    private static final String STRANGELY_UNKNOWN = "unknown??";
    private static final long MEGABYTE = 1048576;

    private static final List<String> PATH_RELATED_KEYS;
    private static final Set<String> IGNORE_THESE_KEYS;

    static {
        final CollectionBuilder<String> pathRelatedKeys = CollectionBuilder.newBuilder();
        pathRelatedKeys.add("sun.boot.class.path");
        pathRelatedKeys.add("com.ibm.oti.vm.bootstrap.library.path");
        pathRelatedKeys.add("java.library.path");
        pathRelatedKeys.add("java.endorsed.dirs");
        pathRelatedKeys.add("java.ext.dirs");
        pathRelatedKeys.add("java.class.path");
        PATH_RELATED_KEYS = pathRelatedKeys.asList();

        final CollectionBuilder<String> ignoreTheseKeys = CollectionBuilder.newBuilder();
        ignoreTheseKeys.addAll(PATH_RELATED_KEYS);
        ignoreTheseKeys.add("line.separator");
        ignoreTheseKeys.add("path.separator");
        ignoreTheseKeys.add("file.separator");
        IGNORE_THESE_KEYS = ignoreTheseKeys.asSet();
    }

    private final FormattedLogMsg logMsg;
    private final BuildUtilsInfo buildUtilsInfo;
    private final OfBizConnectionFactory connectionFactory;
    private final JiraProperties jiraSystemProperties;

    public JiraSystemInfo(final FormattedLogMsg logMsg, final BuildUtilsInfo buildUtilsInfo) {
        this.logMsg = notNull("logMsg", logMsg);
        this.buildUtilsInfo = notNull("buildUtilsInfo", buildUtilsInfo);
        this.connectionFactory = notNull("connectionFactory", DefaultOfBizConnectionFactory.getInstance());
        this.jiraSystemProperties = JiraSystemProperties.getInstance();
    }

    /**
     * This only gets the most basic environment information to avoid bring up the JIRA world before the raw database
     * checks are done.
     * <p/>
     * It MUST BE CAREFUL not to access an JIRA code that will bring up the world
     *
     * @param context - a ServletContext that the app is running in.  This may be nulll
     */
    public void obtainBasicInfo(final ServletContext context) {
        final SystemInfoUtils systemInfoUtils = new SystemInfoUtilsImpl();
        final ReleaseInfo releaseInfo = ReleaseInfo.getReleaseInfo(ReleaseInfo.class);

        logMsg.outputHeader("Environment");

        logMsg.outputProperty("JIRA Build", buildUtilsInfo.getBuildInformation());
        logMsg.outputProperty("Build Date", String.valueOf(buildUtilsInfo.getCurrentBuildDate()));
        logMsg.outputProperty("JIRA Installation Type", releaseInfo.getInfo());

        if (context != null) {
            logMsg.outputProperty("Application Server", context.getServerInfo() + " - Servlet API "
                    + context.getMajorVersion() + "." + context.getMinorVersion());
        }
        logMsg.outputProperty("Java Version", jiraSystemProperties.getProperty("java.version", STRANGELY_UNKNOWN)
                + " - " + jiraSystemProperties.getProperty("java.vendor", STRANGELY_UNKNOWN));
        logMsg.outputProperty("Current Working Directory",
                jiraSystemProperties.getProperty("user.dir", STRANGELY_UNKNOWN));

        final Runtime rt = Runtime.getRuntime();
        final long maxMemory = rt.maxMemory() / MEGABYTE;
        final long totalMemory = rt.totalMemory() / MEGABYTE;
        final long freeMemory = rt.freeMemory() / MEGABYTE;
        final long usedMemory = totalMemory - freeMemory;

        logMsg.outputProperty("Maximum Allowable Memory", maxMemory + "MB");
        logMsg.outputProperty("Total Memory", totalMemory + "MB");
        logMsg.outputProperty("Free Memory", freeMemory + "MB");
        logMsg.outputProperty("Used Memory", usedMemory + "MB");

        for (final MemoryInformation memory : systemInfoUtils.getMemoryPoolInformation()) {
            logMsg.outputProperty("Memory Pool: " + memory.getName(), memory.toString());
        }
        logMsg.outputProperty("JVM Input Arguments", systemInfoUtils.getJvmInputArguments());

        // do we have any patches
        Set<AppliedPatchInfo> appliedPatches = AppliedPatches.getAppliedPatches();
        if (appliedPatches.size() > 0) {
            logMsg.outputHeader("Applied Patches");
            for (AppliedPatchInfo appliedPatch : appliedPatches) {
                logMsg.outputProperty(appliedPatch.getIssueKey(), appliedPatch.getDescription());
            }
        }
        logMsg.outputProperty("Java Compatibility Information", "JIRA version = " + buildUtilsInfo.getVersion()
                + ", Java Version = " + jiraSystemProperties.getProperty("java.version", STRANGELY_UNKNOWN));
    }

    /**
     * Gets basic Java System Properties.  These are safe to access very early on and won't bring up an unintended JIRA
     * code.
     */
    public void obtainSystemProperties() {
        final Properties sysProps = jiraSystemProperties.getProperties();

        @SuppressWarnings("unchecked")
        // cannot use a generified constructor
        final Map<String, String> properties = new TreeMap(sysProps);
        properties.keySet().removeAll(IGNORE_THESE_KEYS);
        logMsg.outputHeader("Java System Properties");
        for (final Map.Entry<String, String> entry : properties.entrySet()) {
            logMsg.outputProperty(entry.getKey(), entry.getValue(), ",");
        }
    }

    public void obtainSystemPathProperties() {
        final Properties sysProps = jiraSystemProperties.getProperties();
        logMsg.outputHeader("Java Class Paths");
        for (final String key : PATH_RELATED_KEYS) {
            final String value = sysProps.getProperty(key, null);
            if (value != null) {
                logMsg.outputProperty(key, value, File.pathSeparator);
                logMsg.add("");
            }
        }
    }

    public void obtainUserDirectoyInfo() {
        if (JiraUtils.isSetup()) {
            CrowdDirectoryService crowdDirectoryService = ComponentAccessor
                    .getComponent(CrowdDirectoryService.class);
            logMsg.outputHeader("User Directories (Ordered)");
            List<Directory> directories = crowdDirectoryService.findAllDirectories();
            for (Directory directory : directories) {
                logMsg.outputProperty(directory.getType().name(), directory.getName());
                logMsg.outputProperty("Implementing Class", directory.getImplementationClass(), 2);
                logMsg.outputProperty("Allowed Operations", StringUtils.join(directory.getAllowedOperations(), ","),
                        ",", 2);
                logMsg.outputProperty("Encryption Type", directory.getEncryptionType(), 2);
                logMsg.outputProperty("Active", String.valueOf(directory.isActive()), 2);
                logMsg.outputProperty("Attributes", "", 2);
                Map<String, String> attributes = directory.getAttributes();
                ArrayList<String> keys = new ArrayList<String>(attributes.keySet());
                Collections.sort(keys);
                for (String key : keys) {
                    String value = key.toLowerCase().contains("password") ? "xxxxxx" : attributes.get(key);
                    logMsg.outputProperty(key, value, 3);
                }
            }
        }
    }

    /**
     * Obtains database configuration information.  This should be called after the database has been checked for sanity
     * and hence we can safely do some entityengine.xml and database connection test. But this is before the database is
     * auto-created and hence the support team can get valuable configuration information before a real DB cockup is
     * encountered.
     */
    public void obtainDatabaseConfigurationInfo() {
        logMsg.outputHeader("Database Configuration");
        final URL entityEngineURL = ClassLoaderUtils.getResource("entityengine.xml", getClass());
        logMsg.outputProperty("Loading entityengine.xml from", entityEngineURL.toString());

        final DatasourceInfo datasourceInfo = connectionFactory.getDatasourceInfo();
        if (datasourceInfo != null) {
            logMsg.outputProperty("Entity model field type name", datasourceInfo.getFieldTypeName());
            logMsg.outputProperty("Entity model schema name", datasourceInfo.getSchemaName());
        }

        Connection connection = null;
        try {
            connection = connectionFactory.getConnection();
            final DatabaseMetaData metaData = connection.getMetaData();
            final SystemInfoUtils jiraSysInfo = new SystemInfoUtilsImpl();

            logMsg.outputProperty("Database Version",
                    metaData.getDatabaseProductName() + " - " + metaData.getDatabaseProductVersion());
            logMsg.outputProperty("Database Driver",
                    metaData.getDriverName() + " - " + metaData.getDriverVersion());
            logMsg.outputProperty("Database URL", maskURL(metaData.getURL()));
            logMsg.outputProperty(jiraSysInfo.getDbDescriptorLabel(), jiraSysInfo.getDbDescriptorValue());
        } catch (final SQLException e) {
            // dont worry about this exception here. Code later one will barf on the same problem and do more appropriate actions.
            // We are just trying to get startup information for support purposes for now.
            log.debug(e);
        } finally {
            silentlyClose(connection);
        }

    }

    /**
     * Only call AFTER JIRA is fully up!
     */
    public void obtainJiraAppProperties() {
        final ReleaseInfo releaseInfo = ReleaseInfo.getReleaseInfo(ReleaseInfo.class);

        final ApplicationProperties applicationProperties = ComponentAccessor.getApplicationProperties();
        /** We want to log this info in English. */
        final ExtendedSystemInfoUtils extendedSystemInfoUtils = new ExtendedSystemInfoUtilsImpl(
                new I18nBean(Locale.ENGLISH));
        final JiraLicenseService jiraLicenseService = ComponentAccessor
                .getComponentOfType(JiraLicenseService.class);

        final String defaultLocal = applicationProperties.getDefaultLocale().getDisplayName();
        final String externalUserManagment = (applicationProperties.getOption(APKeys.JIRA_OPTION_USER_EXTERNALMGT)
                ? "ON"
                : "OFF");
        final String partnerName = buildUtilsInfo.getBuildPartnerName();
        String baseUrl = applicationProperties.getDefaultBackedString(APKeys.JIRA_BASEURL);
        if (StringUtils.isBlank(baseUrl)) {
            baseUrl = "not set";
        }

        logMsg.outputHeader("Core Application Properties");
        logMsg.outputProperty("Version", buildUtilsInfo.getVersion());
        logMsg.outputProperty("Build #", buildUtilsInfo.getCurrentBuildNumber());
        logMsg.outputProperty("Build Date", String.valueOf(buildUtilsInfo.getCurrentBuildDate()));
        logMsg.outputProperty("Installation Type", releaseInfo.getInfo());
        if (StringUtils.isNotEmpty(partnerName)) {
            logMsg.outputProperty("Atlassian Partner", partnerName);
        }
        logMsg.outputProperty("Server ID", jiraLicenseService.getServerId());
        logMsg.outputProperty("Base URL", baseUrl);
        logMsg.outputProperty("Default Language", defaultLocal);
        logMsg.outputProperty("External User Management", externalUserManagment);

        logMsg.outputHeader("Application Properties");
        Map<String, String> allProperties = extendedSystemInfoUtils.getApplicationPropertiesFormatted("\n");
        for (Map.Entry<String, String> entry : allProperties.entrySet()) {
            logMsg.outputProperty(entry.getKey(), entry.getValue());
        }

        obtainLicenseInfo(jiraLicenseService);
    }

    private void obtainLicenseInfo(final JiraLicenseService jiraLicenseService) {
        final LicenseDetails licenseDetails = jiraLicenseService.getLicense();
        logMsg.outputHeader("License Details");
        logMsg.outputProperty("License Set", String.valueOf(licenseDetails.isLicenseSet()));
        if (licenseDetails.isLicenseSet()) {
            logMsg.outputProperty("Entitled To Support", String.valueOf(licenseDetails.isEntitledToSupport()));
            logMsg.outputProperty("Evaluation", String.valueOf(licenseDetails.isEvaluation()));
            logMsg.outputProperty("Description", licenseDetails.getDescription());
            logMsg.outputProperty("SEN", licenseDetails.getSupportEntitlementNumber());
            logMsg.outputProperty("Organisation", licenseDetails.getOrganisation());
            logMsg.outputProperty("Partner", licenseDetails.getPartnerName());
            logMsg.outputProperty("Maximum Number Of Users", licenseDetails.isUnlimitedNumberOfUsers() ? "Unlimited"
                    : String.valueOf(licenseDetails.getMaximumNumberOfUsers()));
        }
        logMsg.add("");
    }

    /**
     * Only call AFTER JIRA is fully up!
     */
    public void obtainDatabaseStatistics() {
        final OfBizDelegator delegator = ComponentAccessor.getOfBizDelegator();
        final long issueCount = delegator.getCount("Issue");
        final long projectCount = delegator.getCount("Project");
        final long customFieldCount = delegator.getCount("CustomField");
        final long workflowCount = delegator.getCount("Workflow");
        final long userCount = delegator.getCount("User");
        final long groupCount = delegator.getCount("Group");
        final long attachmentCount = delegator.getCount(AttachmentConstants.ATTACHMENT_ENTITY_NAME);
        final int commentCount = obtainCommentCountFromIndex();

        logMsg.outputHeader("Database Statistics");
        logMsg.outputProperty("Issues", String.valueOf(issueCount));
        logMsg.outputProperty("Projects", String.valueOf(projectCount));
        logMsg.outputProperty("Custom Fields", String.valueOf(customFieldCount));
        logMsg.outputProperty("Workflows", String.valueOf(workflowCount));
        logMsg.outputProperty("Users", String.valueOf(userCount));
        logMsg.outputProperty("Groups", String.valueOf(groupCount));
        logMsg.outputProperty("Attachments", String.valueOf(attachmentCount));
        logMsg.outputProperty("Comments", String.valueOf(commentCount));
    }

    private int obtainCommentCountFromIndex() {
        final IssueIndexManager indexManager = ComponentAccessor.getComponentOfType(IssueIndexManager.class);
        try {
            return indexManager.getCommentSearcher().getIndexReader().numDocs();
        } catch (SearchUnavailableException ignored) {
            // this will happen during Setup when the lucene indexes are temporarily in a weird state.
            return 0;
        }
    }

    /**
     * Only call AFTER JIRA is fully up!
     */
    public void obtainUpgradeHistory() {
        final UpgradeManager upgradeManager = ComponentAccessor.getComponentOfType(UpgradeManager.class);
        final List<UpgradeHistoryItem> history = upgradeManager.getUpgradeHistory();

        logMsg.outputHeader("Upgrade History");

        if (history.isEmpty()) {
            logMsg.add("No upgrade information is available for this instance.", 1);
            return;
        }

        for (UpgradeHistoryItem upgradeHistoryItem : history) {
            final String targetVersion = String.format("%s%s%s", upgradeHistoryItem.getTargetVersion(), "#",
                    upgradeHistoryItem.getTargetBuildNumber());
            final String originalVersion;
            if (upgradeHistoryItem.getOriginalVersion() != null) {
                originalVersion = String.format("%s%s%s", upgradeHistoryItem.getOriginalVersion(), "#",
                        upgradeHistoryItem.getOriginalBuildNumber());
            } else {
                originalVersion = null;
            }
            final String date;
            if (upgradeHistoryItem.getTimePerformed() != null) {
                date = String.valueOf(upgradeHistoryItem.getTimePerformed());
            } else {
                date = "Unknown";
            }
            logMsg.add(targetVersion, 1);
            logMsg.outputProperty("Time Performed", date, 2);
            if (originalVersion != null) {
                logMsg.outputProperty("Original Version", originalVersion, 2);
            }
            logMsg.add("");
        }
    }

    public void obtainFilePaths() {
        obtainFilePaths(ComponentAccessor.getComponent(JiraHome.class));
    }

    /**
     * Only call AFTER JIRA is fully up!
     *
     * @param jiraHome the JIRA home
     */
    public void obtainFilePaths(final JiraHome jiraHome) {
        final String indexPath = ComponentAccessor.getIndexPathManager().getIndexRootPath();
        final String attachmentPath = ComponentAccessor.getAttachmentPathManager().getAttachmentPath();

        logMsg.outputHeader("File Paths");
        String jiraHomePath;
        try {
            jiraHomePath = jiraHome.getHomePath();
        } catch (IllegalStateException e) {
            jiraHomePath = "";
        }
        logMsg.outputProperty("JIRA Home", jiraHomePath);
        String jiraLocalHomePath;
        try {
            jiraLocalHomePath = jiraHome.getLocalHomePath();
        } catch (IllegalStateException e) {
            jiraLocalHomePath = "";
        }
        logMsg.outputProperty("JIRA Local Home", jiraLocalHomePath);
        logMsg.outputProperty("Location of atlassian-jira.log", getLogPath(jiraHome));
        logMsg.outputProperty("Index Path", indexPath);
        logMsg.outputProperty("Attachment Path", attachmentPath);
    }

    /**
     * Only call AFTER JIRA is fully up!
     */
    public void obtainPlugins() {
        final PluginInfoProvider pluginInfoProvider = ComponentAccessor
                .getComponentOfType(PluginInfoProvider.class);

        logMsg.add(pluginInfoProvider.getUserPlugins().prettyPrint());
        logMsg.add(pluginInfoProvider.getSystemPlugins(false).prettyPrint());
    }

    /**
     * A simple class that allows easier represenation of Listener information, along with some simple sorting. FindBugs
     * made me put the hashCode/equals on it!
     */
    private static class ListenerRepresentation implements Comparable<ListenerRepresentation> {
        private final String name;
        private final String clazz;
        private final GenericValue gv;

        ListenerRepresentation(final GenericValue gv) {
            name = gv.getString("name");
            clazz = gv.getString("clazz");
            this.gv = gv;
        }

        public int compareTo(final ListenerRepresentation that) {
            int rc = name.compareTo(that.name);
            if (rc == 0) {
                rc = clazz.compareTo(that.clazz);
            }
            return rc;
        }

        @Override
        public boolean equals(final Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof ListenerRepresentation)) {
                return false;
            }

            final ListenerRepresentation that = (ListenerRepresentation) o;

            if (name != null ? !name.equals(that.name) : that.name != null) {
                return false;
            }
            if (clazz != null ? !clazz.equals(that.clazz) : that.clazz != null) {
                return false;
            }

            return true;
        }

        @Override
        public int hashCode() {
            int result;
            result = (name != null ? name.hashCode() : 0);
            result = 31 * result + (clazz != null ? clazz.hashCode() : 0);
            return result;
        }
    }

    /**
     * Only call AFTER JIRA is fully up!
     */
    public void obtainListeners() {
        final OfBizDelegator delegator = ComponentAccessor.getOfBizDelegator();
        final List<GenericValue> listenerGVs = delegator.findAll("ListenerConfig");
        final List<ListenerRepresentation> listeners = new ArrayList<ListenerRepresentation>();
        for (final GenericValue gv : listenerGVs) {
            final String name = gv.getString("name");
            if (!name.startsWith("com.atlassian.jira.event,listeners")) {
                listeners.add(new ListenerRepresentation(gv));
            }
        }
        logMsg.outputHeader("Listeners");
        logMsg.outputProperty("Instance Count", String.valueOf(listeners.size()));
        logMsg.add("");

        for (final ListenerRepresentation rep : listeners) {
            logMsg.outputProperty(rep.name, rep.clazz);
            logPropertySet(rep.gv, 2);
        }
    }

    private void logPropertySet(final GenericValue gv, final int indentLevel) {
        final PropertySet propertySet = OFBizPropertyUtils.getCachingPropertySet(gv);
        if (propertySet != null) {
            @SuppressWarnings("unchecked")
            final Collection<String> properties = propertySet.getKeys();
            for (final String key : properties) {
                logMsg.outputProperty(key, String.valueOf(propertySet.getAsActualType(key)), indentLevel);
            }
        }
    }

    /**
     * Only call AFTER JIRA is fully up!
     */
    public void obtainServices() {
        final ServiceManager serviceManager = ComponentAccessor.getComponentOfType(ServiceManager.class);
        final Collection<JiraServiceContainer> services = new TreeSet<JiraServiceContainer>(
                JiraService.NAME_COMPARATOR);
        services.addAll(serviceManager.getServices());

        logMsg.outputHeader("Services");
        logMsg.outputProperty("Instance Count", String.valueOf(services.size()));
        logMsg.add("");

        for (final JiraServiceContainer service : services) {
            printServiceInfo(service);
        }
    }

    private void printServiceInfo(JiraServiceContainer service) {
        logMsg.outputProperty(service.getName(), service.getServiceClass());
        logMsg.outputProperty("Service Delay", String.valueOf(service.getDelay()) + "ms", 2);
        try {
            final long lastRun = service.getLastRun();
            if (lastRun > 0) {
                final DateFormat df = DateFormat.getInstance();
                final Date dt = new Date(lastRun);
                logMsg.outputProperty("Last Run", df.format(dt));
            }
            final Map<String, String> properties = ConfigurableObjectUtil.getPropertyMap(service);
            for (final Map.Entry<String, String> entry : properties.entrySet()) {
                logMsg.outputProperty(entry.getKey(), entry.getValue(), 2);
            }
        } catch (final Exception e) {
            logMsg.outputProperty("Exception getting Service information", e.toString());
        }
    }

    /**
     * Only call AFTER JIRA is fully up!
     * <p/>
     * NOTE : Only PRIVILEGED CODE should make this call.  This code goes to the Trusted Apps Manager directly.  It does
     * this because at JIRA start time, there is no user.  So be careful when calling this method and don't propagate
     * more permissions that are required.
     */
    public void obtainTrustedApps() {
        final TrustedApplicationManager trustedAppManager = ComponentAccessor
                .getComponentOfType(TrustedApplicationManager.class);

        final Collection<TrustedApplicationInfo> trustedApplications = new TreeSet<TrustedApplicationInfo>(
                TrustedApplicationService.NAME_COMPARATOR);
        //
        // PRIVILEGED CALL HERE
        trustedApplications.addAll(trustedAppManager.getAll());

        logMsg.outputHeader("Trusted Applications");
        logMsg.outputProperty("Instance Count", String.valueOf(trustedApplications.size()));
        logMsg.add("");

        for (final Object element : trustedApplications) {
            final TrustedApplicationInfo applicationInfo = (TrustedApplicationInfo) element;
            logMsg.outputProperty("Application Name", applicationInfo.getName());
            logMsg.outputProperty("Matching URLs", applicationInfo.getUrlMatch(), 2);
            logMsg.outputProperty("Matching IP", applicationInfo.getIpMatch(), 2);
        }
    }

    /*====================================================*/

    private void silentlyClose(final Connection connection) {
        if (connection != null) {
            try {
                connection.close();
            } catch (final SQLException e) {
                // ignore silently
            }
        }
    }

    private String maskURL(final String url) {
        if (url == null) {
            return null;
        }
        final Pattern passwordRegex = Pattern.compile("password=[^&;]*", Pattern.CASE_INSENSITIVE);
        return passwordRegex.matcher(url).replaceAll("password=****");
    }

    private String getLogPath(final JiraHome jiraHome) {
        final JiraLogLocator locator = new JiraLogLocator(jiraHome);
        final File logFile = locator.findJiraLogFile();
        if (logFile != null) {
            return logFile.getAbsolutePath();
        } else {
            return STRANGELY_UNKNOWN;
        }
    }
}