org.codice.ddf.admin.core.impl.SystemPropertiesAdmin.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.admin.core.impl.SystemPropertiesAdmin.java

Source

/**
 * Copyright (c) Codice Foundation
 *
 * <p>This is free software: you can redistribute it and/or modify it under the terms of the GNU
 * Lesser General Public License as published by the Free Software Foundation, either version 3 of
 * the License, or any later version.
 *
 * <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details. A copy of the GNU Lesser General Public
 * License is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package org.codice.ddf.admin.core.impl;

import static org.codice.gsonsupport.GsonTypeAdapters.MAP_STRING_TO_OBJECT_TYPE;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.StandardMBean;
import org.apache.commons.io.FileUtils;
import org.apache.felix.utils.properties.Properties;
import org.codice.ddf.admin.core.api.SystemPropertyDetails;
import org.codice.ddf.admin.core.api.jmx.SystemPropertiesAdminInterceptor;
import org.codice.ddf.admin.core.api.jmx.SystemPropertiesAdminMBean;
import org.codice.ddf.configuration.SystemBaseUrl;
import org.codice.ddf.configuration.SystemInfo;
import org.codice.gsonsupport.GsonTypeAdapters.LongDoubleTypeAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SystemPropertiesAdmin extends StandardMBean implements SystemPropertiesAdminMBean {

    public static final String HTTP_PROTOCOL = "http://";
    public static final String HTTPS_PROTOCOL = "https://";
    private static final String DEFAULT_LOCALHOST_DN = "localhost.local";
    private static final String KARAF_ETC = "karaf.etc";
    private static final String LOCAL_HOST = "localhost";
    private static final String SYSTEM_PROPERTIES_FILE = "custom.system.properties";
    private static final String USERS_PROPERTIES_FILE = "users.properties";
    private static final String USERS_ATTRIBUTES_FILE = "users.attributes";
    private static final String EXTERNAL_HOST_TITLE = "External Host";
    private static final String INTERNAL_HOST_TITLE = "Internal Host";
    private static final String INTERNAL_HOST_DESCRIPTION = "The hostname or IP address this system runs on. NOTE: This setting will take effect after a system restart.";
    private static final String EXTERNAL_HOST_DESCRIPTION = "The host name or IP address used to advertise the system. Possibilities include the address of a single node of that of a load balancer in a multi-node deployment. NOTE: This setting will take effect after a system restart.";
    private static final ArrayList<String> PROTOCOL_OPTIONS = new ArrayList<>();
    private static final String EXTERNAL_HTTP_PORT_TITLE = "External HTTP Port";
    private static final String INTERNAL_HTTP_PORT_TITLE = "Internal HTTP Port";
    private static final String EXTERNAL_HTTP_PORT_DESCRIPTION = "The port used to advertise the system. Possibilities include the port of a single node of that of a load balancer in a multi-node deployment. NOTE: This setting will take effect after a system restart.";
    private static final String INTERNAL_HTTP_PORT_DESCRIPTION = "The http port that the system uses. NOTE: This *DOES* change the port the system runs on.";
    private static final String EXTERNAL_HTTPS_PORT_TITLE = "External HTTPS Port";
    private static final String INTERNAL_HTTPS_PORT_TITLE = "Internal HTTPS Port";
    private static final String EXTERNAL_HTTPS_PORT_DESCRIPTION = "The secure port used to advertise the system. Possibilities include the port of a single node of that of a load balancer in a multi-node deployment. NOTE: This setting will take effect after a system restart.";
    private static final String INTERNAL_HTTPS_PORT_DESCRIPTION = "The https port that the system uses. NOTE: This *DOES* change the port the system runs on.";
    private static final String ORGANIZATION_TITLE = "Organization";
    private static final String ORGANIZATION_DESCRIPTION = "The name of the organization that runs this instance.";
    private static final String SITE_CONTACT_TITLE = "Site Contact";
    private static final String SITE_CONTACT_DESCRIPTION = "The email address of the site contact.";
    private static final String SITE_NAME_TITLE = "Site Name";
    private static final String SITE_NAME_DESCRIPTION = "The unique name of this instance. This name will be provided via web services that ask for the name.";
    private static final String VERSION_TITLE = "Version";
    private static final String VERSION_DESCRIPTION = "The version of this instance.";
    private static final String LOCALHOST_DATA_MANAGER = "localhost-data-manager";
    private static final String DATA_MANAGER = "data-manager";

    private String etcDir = System.getProperty(KARAF_ETC);
    private String systemPropertyFilename = etcDir + File.separator + SYSTEM_PROPERTIES_FILE;
    private String userPropertiesFilename = etcDir + File.separator + USERS_PROPERTIES_FILE;
    private String userAttributesFilename = etcDir + File.separator + USERS_ATTRIBUTES_FILE;

    private File systemPropertiesFile = new File(systemPropertyFilename);
    private File userPropertiesFile = new File(userPropertiesFilename);
    private File userAttributesFile = new File(userAttributesFilename);

    private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting()
            .registerTypeAdapterFactory(LongDoubleTypeAdapter.FACTORY).create();

    private static final Logger LOGGER = LoggerFactory.getLogger(SystemPropertiesAdmin.class);

    static {
        PROTOCOL_OPTIONS.add(HTTPS_PROTOCOL);
        PROTOCOL_OPTIONS.add(HTTP_PROTOCOL);
    }

    public List<SystemPropertiesAdminInterceptor> getSystemPropertiesAdminInterceptors() {
        return systemPropertiesAdminInterceptors;
    }

    public void setSystemPropertiesAdminInterceptors(
            List<SystemPropertiesAdminInterceptor> systemPropertiesAdminInterceptors) {
        this.systemPropertiesAdminInterceptors = systemPropertiesAdminInterceptors;
    }

    private List<SystemPropertiesAdminInterceptor> systemPropertiesAdminInterceptors;
    private MBeanServer mbeanServer;
    private ObjectName objectName;
    private String oldHostName = SystemBaseUrl.INTERNAL.getHost();
    private GuestClaimsHandlerExt guestClaimsHandlerExt;

    public SystemPropertiesAdmin(GuestClaimsHandlerExt guestClaimsHandlerExt) throws NotCompliantMBeanException {
        super(SystemPropertiesAdminMBean.class);
        this.guestClaimsHandlerExt = guestClaimsHandlerExt;
        configureMBean();
    }

    @Override
    public List<SystemPropertyDetails> readSystemProperties() {
        LOGGER.debug("get system properties");

        ArrayList<SystemPropertyDetails> properties = new ArrayList<>();

        Properties systemDotProperties = null;
        try {
            systemDotProperties = new Properties(systemPropertiesFile);
            properties.add(getSystemPropertyDetails(SystemBaseUrl.EXTERNAL_HOST, EXTERNAL_HOST_TITLE,
                    EXTERNAL_HOST_DESCRIPTION, null, systemDotProperties));
            properties.add(getSystemPropertyDetails(SystemBaseUrl.EXTERNAL_HTTP_PORT, EXTERNAL_HTTP_PORT_TITLE,
                    EXTERNAL_HTTP_PORT_DESCRIPTION, null, systemDotProperties));
            properties.add(getSystemPropertyDetails(SystemBaseUrl.EXTERNAL_HTTPS_PORT, EXTERNAL_HTTPS_PORT_TITLE,
                    EXTERNAL_HTTPS_PORT_DESCRIPTION, null, systemDotProperties));
            properties.add(getSystemPropertyDetails(SystemBaseUrl.INTERNAL_HOST, INTERNAL_HOST_TITLE,
                    INTERNAL_HOST_DESCRIPTION, null, systemDotProperties));
            properties.add(getSystemPropertyDetails(SystemBaseUrl.INTERNAL_HTTP_PORT, INTERNAL_HTTP_PORT_TITLE,
                    INTERNAL_HTTP_PORT_DESCRIPTION, null, systemDotProperties));
            properties.add(getSystemPropertyDetails(SystemBaseUrl.INTERNAL_HTTPS_PORT, INTERNAL_HTTPS_PORT_TITLE,
                    INTERNAL_HTTPS_PORT_DESCRIPTION, null, systemDotProperties));
            properties.add(getSystemPropertyDetails(SystemInfo.ORGANIZATION, ORGANIZATION_TITLE,
                    ORGANIZATION_DESCRIPTION, null, systemDotProperties));
            properties.add(getSystemPropertyDetails(SystemInfo.SITE_CONTACT, SITE_CONTACT_TITLE,
                    SITE_CONTACT_DESCRIPTION, null, systemDotProperties));
            properties.add(getSystemPropertyDetails(SystemInfo.SITE_NAME, SITE_NAME_TITLE, SITE_NAME_DESCRIPTION,
                    null, systemDotProperties));
            properties.add(getSystemPropertyDetails(SystemInfo.VERSION, VERSION_TITLE, VERSION_DESCRIPTION, null,
                    systemDotProperties));
        } catch (IOException e) {
            LOGGER.warn("Exception while reading the system.properties file.", e);
        }

        return properties;
    }

    @Override
    public void writeSystemProperties(Map<String, String> updatedSystemProperties) {
        if (updatedSystemProperties == null) {
            return;
        }

        Properties systemDotProperties;
        try {
            systemDotProperties = new Properties(systemPropertiesFile);

        } catch (IOException e) {
            LOGGER.warn("Exception reading system.properties file.", e);
            return;
        }

        // save off the current/old hostname before we make any changes
        oldHostName = systemDotProperties.getProperty(SystemBaseUrl.INTERNAL_HOST);
        updatedSystemProperties.forEach((key, value) -> {
            // Clears out the property value before setting it
            //
            // We have to do this because when we read in the properties, the values are
            // expanded which can lead to a state where the value is erroneously not updated
            // after being checked.
            systemDotProperties.put(key, "");
            systemDotProperties.put(key, value);
        });

        callInterceptors(systemDotProperties);

        try {
            systemDotProperties.save();
        } catch (IOException e) {
            LOGGER.warn("Exception writing to system.properties file.", e);
        }

        writeOutUsersDotPropertiesFile(userPropertiesFile);
        writeOutUsersDotAttributesFile(userAttributesFile);
    }

    private void callInterceptors(Properties systemPropertiesFile) {
        if (getSystemPropertiesAdminInterceptors() != null) {
            for (SystemPropertiesAdminInterceptor each : getSystemPropertiesAdminInterceptors()) {
                if (each != null) {
                    each.updateSystemProperties(systemPropertiesFile);
                }
            }
        }
    }

    /*
     * Writes user property data to the relevant file after replacing the default hostname where
     * necessary.
     */
    private void writeOutUsersDotPropertiesFile(File userPropertiesFile) {
        try {
            Properties usersDotProperties = new Properties(userPropertiesFile);

            if (!usersDotProperties.isEmpty()) {
                String oldHostValue = usersDotProperties.getProperty(oldHostName);

                if (oldHostValue != null) {
                    Properties systemDotProperties = new Properties(systemPropertiesFile);
                    String newInternalHost = systemDotProperties.getProperty(SystemBaseUrl.INTERNAL_HOST);
                    String newHostValue = oldHostValue.replaceAll(LOCALHOST_DATA_MANAGER,
                            String.format("%s-%s", newInternalHost, DATA_MANAGER));
                    usersDotProperties.remove(oldHostName);
                    usersDotProperties.setProperty(newInternalHost, newHostValue);
                    usersDotProperties.save();
                }
            }
        } catch (IOException e) {
            LOGGER.warn("Exception while writing to users.properties file.", e);
        }
    }

    /*
     * Writes security and claims data for the system-level user and default admin.
     */
    private void writeOutUsersDotAttributesFile(File userAttributesFile) {
        Map<String, Object> json = null;
        try (BufferedReader br = Files.newBufferedReader(Paths.get(userAttributesFile.toURI()))) {
            json = GSON.fromJson(br, MAP_STRING_TO_OBJECT_TYPE);
        } catch (IOException e) {
            LOGGER.warn("Unable to read system user attribute file for hostname update.", e);
            return;
        }

        addGuestClaimsProfileAttributes(json);

        if (json.containsKey(oldHostName)) {
            Properties systemDotProperties = null;
            try {
                systemDotProperties = new Properties(systemPropertiesFile);
                json.put(systemDotProperties.get(SystemBaseUrl.INTERNAL_HOST), json.remove(oldHostName));
            } catch (IOException e) {
                LOGGER.warn("Exception while reading the system.properties file.", e);
            }
        }

        try {
            for (Map.Entry<String, Object> entry : json.entrySet()) {
                json.put(entry.getKey(), replaceLocalhost(entry.getValue()));
            }
            FileUtils.writeStringToFile(userAttributesFile, GSON.toJson(json), Charset.defaultCharset());
        } catch (IOException e) {
            LOGGER.warn("Unable to write user attribute file for system update.", e);
        }
    }

    /** Overwrite attributes with those from GuestClaimsHandlerExt so system high is set. */
    private void addGuestClaimsProfileAttributes(Map<String, Object> json) {
        Map<String, Object> selectedProfileAttributes = guestClaimsHandlerExt.getProfileSystemClaims();
        if (selectedProfileAttributes != null) {
            Map<String, Object> localhost = (Map<String, Object>) json.get(LOCAL_HOST);
            if (localhost != null) {
                localhost.putAll(selectedProfileAttributes);
            }
        }
    }

    /*
     * Replace any instances of SystemPropertiesAdmin#DEFAULT_LOCALHOST_DN with the actual
     * hostname.
     */
    @SuppressWarnings("unchecked")
    private Object replaceLocalhost(Object hostMap) throws IOException {
        Properties systemDotProperties = new Properties(systemPropertiesFile);

        if (!(hostMap instanceof Map)) {
            return hostMap;
        }

        Map<String, Object> map;
        try {
            map = (Map<String, Object>) hostMap;
        } catch (ClassCastException e) {
            return hostMap;
        }

        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (!(entry.getValue() instanceof String)) {
                continue;
            }
            String val = (String) entry.getValue();
            if (val.contains(DEFAULT_LOCALHOST_DN)) {
                map.put(entry.getKey(), val.replace(DEFAULT_LOCALHOST_DN,
                        systemDotProperties.getProperty(SystemBaseUrl.INTERNAL_HOST)));
            }
        }
        return map;
    }

    private SystemPropertyDetails getSystemPropertyDetails(String key, String title, String description,
            List<String> options, Properties properties) {
        String property = properties.getProperty(key);
        return new SystemPropertyDetailsImpl(title, description, options, key, property);
    }

    private void configureMBean() {
        mbeanServer = ManagementFactory.getPlatformMBeanServer();

        try {
            objectName = new ObjectName(SystemPropertiesAdminMBean.OBJECT_NAME);
        } catch (MalformedObjectNameException e) {
            LOGGER.debug("Exception while creating object name: " + SystemPropertiesAdminMBean.OBJECT_NAME, e);
        }

        try {
            registerSystemPropertiesAdminMBean();
        } catch (Exception e) {
            LOGGER.info("Could not register mbean.", e);
        }
    }

    private void registerSystemPropertiesAdminMBean() throws MBeanRegistrationException, NotCompliantMBeanException,
            InstanceNotFoundException, InstanceAlreadyExistsException {
        try {
            mbeanServer.registerMBean(new StandardMBean(this, SystemPropertiesAdminMBean.class), objectName);
        } catch (InstanceAlreadyExistsException e) {
            mbeanServer.unregisterMBean(objectName);
            mbeanServer.registerMBean(new StandardMBean(this, SystemPropertiesAdminMBean.class), objectName);
        }
    }

    public void shutdown() throws MBeanRegistrationException {
        try {
            if (objectName != null && mbeanServer != null) {
                mbeanServer.unregisterMBean(objectName);
            }
        } catch (InstanceNotFoundException | MBeanRegistrationException e) {
            throw new MBeanRegistrationException(e, "Exception unregistering mbean");
        }
    }
}