org.wyona.yanel.core.map.RealmManager.java Source code

Java tutorial

Introduction

Here is the source code for org.wyona.yanel.core.map.RealmManager.java

Source

/*
 * Copyright 2006 Wyona
 *
 *  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.wyona.org/licenses/APACHE-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 org.wyona.yanel.core.map;

import java.io.File;
import java.io.IOException;
import java.lang.ClassNotFoundException;
import java.net.URI;
import java.net.URL;
import java.net.URLDecoder;
import java.util.LinkedHashMap;
import java.util.Properties;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationUtil;
import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
import org.apache.avalon.framework.configuration.DefaultConfigurationSerializer;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.wyona.commons.io.FileUtil;
import org.wyona.yanel.core.ConfigurationException;
import org.wyona.yanel.core.Yanel;
import org.wyona.yarep.core.RepositoryFactory;

/**
 * Class managing registration of realms (adding, updating, copying, deleting, ...)
 */
public class RealmManager {

    private static Logger log = LogManager.getLogger(RealmManager.class);

    private static String YANEL_CONFIGURATION_FILE = Yanel.DEFAULT_CONFIGURATION_FILE;

    public static String REALM_DEFAULT_CONFIG_NAME = "realm.xml";

    private URL propertiesURL;
    private File yanelConfigFile;

    private File _realmsConfigFile;

    private LinkedHashMap _realms = new LinkedHashMap();
    private Realm rootRealm = null;

    /**
     *
     */
    public RealmManager() throws ConfigurationException {
        this(Yanel.DEFAULT_CONFIGURATION_FILE_XML);
    }

    /**
     * Initializes realms
     * @param yanelConfigurationFilename Yanel configuration filename, either 'yanel.xml' or 'yanel.properties'
     */
    public RealmManager(String yanelConfigurationFilename) throws ConfigurationException {
        log.debug("Yanel Configuration Filename: " + yanelConfigurationFilename);
        File realmsConfigFile = getRealmsConfigFile(yanelConfigurationFilename);
        // INFO: Set private realms config file
        _realmsConfigFile = realmsConfigFile;
        log.debug("Realms Configuration: " + realmsConfigFile);
        readRealms(realmsConfigFile);
    }

    /**
     * Get realms configuration file (either based on yanel configuration or based on environment variable)
     * @param yanelConfigurationFilename Yanel configuration filename, either 'yanel.xml' or 'yanel.properties'
     * @return Realms configuration file, either something like /usr/local/tomcat/webapps/yanel/WEB-INF/classes/realms.xml or /home/foo/realms.xml
     */
    private File getRealmsConfigFile(String yanelConfigurationFilename) throws ConfigurationException {

        // 1.) Getting realms.xml from environment variable YANEL_REALMS_HOME
        java.util.Map<String, String> env = System.getenv();
        for (java.util.Map.Entry envEntry : env.entrySet()) {
            if (((String) envEntry.getKey()).equals("YANEL_REALMS_HOME")) {
                File yanelRealmsHome = new File((String) envEntry.getValue());
                if (yanelRealmsHome.isDirectory()) {
                    File envRealmsConfigFile = new File(yanelRealmsHome, "realms.xml");
                    if (envRealmsConfigFile.isFile()) {
                        log.warn("Use environment variable YANEL_REALMS_HOME '" + yanelRealmsHome
                                + "' in order to load realms configuration.");
                        return envRealmsConfigFile;
                    } else {
                        log.warn("No realms configuration found: " + envRealmsConfigFile.getAbsolutePath());
                    }
                    break;
                }
            }
        }

        // 2.) Getting realms.xml from user home directory or rather hidden yanel directory inside user home directory
        log.debug("User home directory: " + System.getProperty("user.home"));

        File userHomeDotYanelRealmsConfigFile = new File(System.getProperty("user.home") + "/.yanel", "realms.xml");
        if (userHomeDotYanelRealmsConfigFile.isFile()) {
            log.warn("DEBUG: Use hidden folder inside user home directory: "
                    + userHomeDotYanelRealmsConfigFile.getParentFile().getAbsolutePath());
            return userHomeDotYanelRealmsConfigFile;
        } else {
            log.warn("No realms configuration found inside hidden folder at user home directory: "
                    + userHomeDotYanelRealmsConfigFile.getAbsolutePath());
        }

        File userHomeRealmsConfigFile = new File(System.getProperty("user.home"), "realms.xml");
        if (userHomeRealmsConfigFile.isFile()) {
            log.warn("DEPRECATED: Use user home directory: " + System.getProperty("user.home"));
            return userHomeRealmsConfigFile;
        } else {
            log.warn("No realms configuration found within user home directory: "
                    + userHomeRealmsConfigFile.getAbsolutePath());
        }

        // 3.) Getting realms.xml from http://tomcat.apache.org/tomcat-5.5-doc/config/context.html#Environment_Entries
        String envEntryPath = "java:comp/env/yanel/realms-config-file";
        try {
            javax.naming.InitialContext ic = new javax.naming.InitialContext();
            if (ic.lookup(envEntryPath) != null) {
                log.warn("realms.xml set as environment entry: " + (String) ic.lookup(envEntryPath));
                return new File((String) ic.lookup(envEntryPath));
            } else {
                log.info("No enviroment entry '" + envEntryPath + "' set.");
            }
        } catch (Exception e) {
            log.info("No enviroment entry '" + envEntryPath + "' set.");
        }

        // 4.) Getting realms.xml from yanel.xml
        YANEL_CONFIGURATION_FILE = yanelConfigurationFilename;

        if (RealmManager.class.getClassLoader().getResource(YANEL_CONFIGURATION_FILE) == null) {
            log.warn("No such configuration file '" + YANEL_CONFIGURATION_FILE
                    + "' in classpath, hence use default filename: " + Yanel.DEFAULT_CONFIGURATION_FILE);
            YANEL_CONFIGURATION_FILE = Yanel.DEFAULT_CONFIGURATION_FILE;
        }

        File realmsConfigFile = null;
        if (RealmManager.class.getClassLoader().getResource(YANEL_CONFIGURATION_FILE) != null) {
            if (YANEL_CONFIGURATION_FILE.endsWith(".xml")) {

                try {
                    URI configFileUri = new URI(
                            RealmManager.class.getClassLoader().getResource(YANEL_CONFIGURATION_FILE).toString());
                    yanelConfigFile = new File(configFileUri.getPath());
                } catch (Exception e) {
                    String errorMsg = "Failure while reading configuration: " + e.getMessage();
                    log.error(errorMsg, e);
                    throw new ConfigurationException(errorMsg, e);
                }

                try {
                    DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
                    Configuration config;
                    config = builder.buildFromFile(yanelConfigFile);

                    realmsConfigFile = new File(config.getChild("realms-config").getAttribute("src"));
                } catch (Exception e) {
                    String errorMsg = "Failure while reading configuration: " + e.getMessage();
                    log.error(errorMsg, e);
                    throw new ConfigurationException(errorMsg, e);
                }
                if (!realmsConfigFile.isAbsolute()) {
                    realmsConfigFile = FileUtil.file(yanelConfigFile.getParentFile().getAbsolutePath(),
                            realmsConfigFile.toString());
                }
            } else if (YANEL_CONFIGURATION_FILE.endsWith("properties")) {
                propertiesURL = RealmManager.class.getClassLoader().getResource(YANEL_CONFIGURATION_FILE);
                if (propertiesURL == null) {
                    String errMessage = "No such resource: " + YANEL_CONFIGURATION_FILE;
                    log.error(errMessage);
                    throw new ConfigurationException(errMessage);
                }
                Properties props = new Properties();
                try {
                    props.load(propertiesURL.openStream());
                    // use URLDecoder to avoid problems when the filename contains spaces, see
                    // http://bugzilla.wyona.com/cgi-bin/bugzilla/show_bug.cgi?id=5165
                    File propsFile = new File(URLDecoder.decode(propertiesURL.getFile()));

                    realmsConfigFile = new File(props.getProperty("realms-config"));
                    if (!realmsConfigFile.isAbsolute()) {
                        realmsConfigFile = FileUtil.file(propsFile.getParentFile().getAbsolutePath(),
                                realmsConfigFile.toString());
                    }
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                    throw new ConfigurationException("Could not load realms configuration file: " + propertiesURL);
                }
            } else {
                log.error(YANEL_CONFIGURATION_FILE + " has to be either '.xml' or '.properties'");
            }
        } else {
            log.error("No such configuration file in classpath: " + YANEL_CONFIGURATION_FILE);
        }

        if (realmsConfigFile == null) {
            throw new ConfigurationException("Realms configuration file could not be determined!");
        }

        return realmsConfigFile;
    }

    /**
     * Get yanel configuration file
     * @deprecated
     */
    public String getConfigurationFile() {
        return YANEL_CONFIGURATION_FILE;
    }

    /**
     * Get realms configuration file
     */
    public String getRealmsConfigurationFile() {
        if (_realmsConfigFile != null && _realmsConfigFile.exists()) {
            return _realmsConfigFile.getAbsolutePath();
        } else {
            log.error("Either no realms configuration file was set or it does not exist: " + _realmsConfigFile);
            return null;
        }
    }

    /**
     * @deprecated Use readRealms(File) instead
     * Read realms configuration
     */
    public void readRealms() throws ConfigurationException {
        log.warn("DEPRECATED");
        readRealms(_realmsConfigFile);
    }

    /**
     * Read realm configurations
     * @param realmsConfigFile Realms configuration file
     */
    public void readRealms(File realmsConfigFile) throws ConfigurationException {
        _realms = new LinkedHashMap();
        rootRealm = null;

        DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
        Configuration config;

        Yanel yanel;
        try {
            yanel = Yanel.getInstance();
        } catch (Exception e) {
            String errorMsg = "Could not initialize yanel: " + e.getMessage();
            log.error(errorMsg, e);
            throw new ConfigurationException(errorMsg, e);
        }

        try {
            log.debug("Get default repo factory ...");
            RepositoryFactory defaultRepoFactory = yanel
                    .getRepositoryFactory(RealmDefaultImpl.DEFAULT_REPOSITORY_FACTORY_BEAN_ID);
            defaultRepoFactory.reset();
            log.debug("Get default repo factory DONE.");

            RepositoryFactory rtiRepoFactory = yanel.getRepositoryFactory("RTIRepositoryFactory");
            rtiRepoFactory.reset();

            RepositoryFactory policiesRepoFactory = yanel.getRepositoryFactory("ACPoliciesRepositoryFactory");
            policiesRepoFactory.reset();

            RepositoryFactory identitiesRepoFactory = yanel.getRepositoryFactory("ACIdentitiesRepositoryFactory");
            identitiesRepoFactory.reset();

            log.info("Read realms configuration: " + realmsConfigFile);
            RealmContextConfig[] rcc = new RealmManagerConfig().getRealmContextConfigs(realmsConfigFile);
            for (int i = 0; i < rcc.length; i++) {
                String mountPoint = rcc[i].getMountPoint();
                String realmId = rcc[i].getID();
                String realmLabel = rcc[i].getLabel();

                File realmConfigFile = resolveFile(rcc[i].getUnresolvedConfigurationFile(), realmsConfigFile);
                if (realmConfigFile.isDirectory()) {
                    realmConfigFile = new File(realmConfigFile, REALM_DEFAULT_CONFIG_NAME);
                }

                Realm realm;
                try {
                    log.info("Reading realm configuration file for [" + realmId + "]: " + realmConfigFile);
                    Configuration realmConfig = builder.buildFromFile(realmConfigFile);
                    try {
                        String customRealmImplClassName = realmConfig.getAttribute("class");
                        Class[] classArgs = new Class[] { String.class, String.class, String.class, File.class };
                        Object[] values = new Object[4];
                        values[0] = realmLabel;
                        values[1] = realmId;
                        values[2] = mountPoint;
                        values[3] = realmConfigFile;
                        java.lang.reflect.Constructor ct = Class.forName(customRealmImplClassName)
                                .getConstructor(classArgs);
                        realm = (Realm) ct.newInstance(values);
                    } catch (ClassNotFoundException e) {
                        log.error("Class not found: " + e.getMessage()
                                + ". Fallback to default realm implementation!");
                        realm = new RealmDefaultImpl(realmLabel, realmId, mountPoint, realmConfigFile);
                    } catch (Exception e) {
                        log.info(
                                "Default realm implementation will be used, because no custom realm implementation configured for realm '"
                                        + realmId + "'.");
                        realm = new RealmDefaultImpl(realmLabel, realmId, mountPoint, realmConfigFile);
                    }

                    ReverseProxyConfig rpc = rcc[i].getReverseProxyConfig();
                    if (rpc != null) {
                        realm.setReverseProxyConfig(rpc);
                    }
                } catch (Exception e) {
                    String errorMsg = "Error setting up realm [" + realmId + "]: '" + realmConfigFile + "': " + e;
                    log.error(errorMsg, e);
                    // NOTE: Do not throw an exception, because otherwise all other realms are not being loaded either
                    //throw new ConfigurationException(errorMsg, e);
                    realm = new RealmWithConfigurationExceptionImpl(realmLabel, realmId, mountPoint,
                            realmConfigFile, e);
                }

                realm.setUserTrackingDomain(rcc[i].getUserTrackingDomain());

                log.info("Realm: " + realm);
                _realms.put(realmId, realm);
                if (rcc[i].isRoot()) {
                    log.debug("Root realm found: " + realm.getID());
                    if (rootRealm == null) {
                        log.debug("Root realm set: " + realm.getID());
                        rootRealm = realm;
                    } else {
                        // TODO: In what cases does this happen?
                        log.warn("Root realm has already been set: " + realmId);
                    }
                }
            }
            log.info("All realm configurations have been read.");
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ConfigurationException("Error setting up realms from file " + realmsConfigFile + ": " + e, e);
        }
        if (rootRealm != null) {
            inheritRootRealmProperties();
        } else {
            throw new ConfigurationException("No root realm configured!");
        }
    }

    /**
     * If the given file has a relative path, resolve it relative to the given dir.
     * If dir is in fact a file, the resolving will use the parent dir of that file.  
     * @param file Unresolved realm configuration specified within realms.xml
     * @param dir Path of realms.xml
     */
    protected File resolveFile(File file, File dir) {
        // TODO: Resolve javax.servlet.context
        // TODO: Replace this method by some method from org.wyona.commons.io.FileUtil ...
        if (!file.isAbsolute()) {
            if (dir.isDirectory()) {
                file = new File(org.apache.commons.io.FilenameUtils.concat(dir.getAbsolutePath(), file.getPath()));
            } else {
                file = new File(org.apache.commons.io.FilenameUtils.concat(dir.getParent(), file.getPath()));
            }
        }
        return file;
    }

    /**
     * Get realm
     * @param id Realm ID
     */
    public Realm getRealm(String id) {
        return (Realm) _realms.get(id);
    }

    /**
     * Get all realms in the order they given in the (local.)yanel.properties file.
     */
    public Realm[] getRealms() {
        Realm[] realms = new Realm[_realms.size()];
        return realms = (Realm[]) _realms.values().toArray(realms);
    }

    /**
     * Get root realm
     */
    public Realm getRootRealm() {
        return rootRealm;
    }

    /**
     * Adds a new realm which already physically exists in the filesystem.
     * The new realm will be added to realms.xml and registered in this RealmManager.
     * @param realmID Realm ID
     * @param realmName Realm name
     */
    public void addRealm(String realmID, String realmName, String mountPoint, String configFileSrc)
            throws Exception {
        if (getRealm(realmID) != null) {
            log.warn("Cannot add realm: " + realmID + " (realm with this ID exists already)");
            throw new Exception("Cannot add realm: " + realmID + " (realm with this ID exists already)");
        }
        log.debug("adding realm: " + realmID);
        DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
        Configuration config = builder.buildFromFile(_realmsConfigFile);
        Element documentElement = ConfigurationUtil.toElement(config);
        Document document = documentElement.getOwnerDocument();

        Element newRealmElement = document.createElementNS("http://www.wyona.org/yanel/1.0", "realm");
        newRealmElement.setAttribute("id", realmID);
        newRealmElement.setAttribute("mount-point", mountPoint);
        Element newNameElement = document.createElementNS("http://www.wyona.org/yanel/1.0", "name");
        newNameElement.appendChild(document.createTextNode(realmName));
        newRealmElement.appendChild(newNameElement);
        Element newConfigElement = document.createElementNS("http://www.wyona.org/yanel/1.0", "config");
        newConfigElement.setAttribute("src", configFileSrc);
        newRealmElement.appendChild(newConfigElement);

        documentElement.appendChild(newRealmElement);
        config = ConfigurationUtil.toConfiguration(documentElement);

        DefaultConfigurationSerializer configSerializer = new DefaultConfigurationSerializer();
        configSerializer.setIndent(true);
        configSerializer.serializeToFile(_realmsConfigFile, config);

        // INFO: reload the realm configuration
        readRealms(_realmsConfigFile);
    }

    /**
     * Inherit properties of root realm to other realms
     */
    private void inheritRootRealmProperties() {
        // TODO: Use entrySet
        java.util.Iterator keyIterator = _realms.keySet().iterator();
        while (keyIterator.hasNext()) {
            String key = (String) keyIterator.next();
            Realm realm = (Realm) _realms.get(key);
            if (!realm.getID().equals(rootRealm.getID())) {
                log.debug("Check whether to inherit root realm properties to another realm: " + realm.getName());
                if ((realm.getProxyHostName() == null) && (!key.equals(rootRealm.getID()))
                        && rootRealm.isProxySet()) {
                    realm.setReverseProxyConfig(rootRealm.getReverseProxyConfig());
                    log.info("Inherit root realm properties to realm: " + key);
                }
                if (realm.getIdentityManager() == null) {
                    log.info("Realm \"" + realm.getName() + "\" will inherit IdentityManager of root realm!");
                    realm.setIdentityManager(rootRealm.getIdentityManager());
                }
                if (realm.getPolicyManager() == null) {
                    log.warn("Realm \"" + realm.getName() + "\" will inherit PolicyManager of root realm!");
                    realm.setPolicyManager(rootRealm.getPolicyManager());
                }
            }
        }
    }

    /**
     * Copies a realm by creating a physical copy of the realm, changing its name/id,
     * and registering it in this RealmManager.
     * A realm can only be copied if it has a <root-dir> element in its config file and
     * if this directory contains the complete realm. 
     * @param srcRealmID
     * @param destRealmID
     * @param destRealmName
     * @param destMountPoint
     * @param destDir directory where the new realm will be created (if null, the realm
     *                will be created in the same directory as the src realm).
     * @throws Exception
     */
    public void copyRealm(String srcRealmID, String destRealmID, String destRealmName, String destMountPoint,
            File destDir) throws Exception {
        if (getRealm(destRealmID) != null) {
            log.warn("Cannot add realm: " + destRealmID + " (realm with this ID exists already)");
            throw new Exception("Cannot add realm: " + destRealmID + " (realm with this ID exists already)");
        }
        DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();

        Realm srcRealm = getRealm(srcRealmID);
        if (srcRealm == null) {
            throw new Exception(
                    "cannot create realm '" + destRealmID + "': source realm '" + srcRealmID + "' does not exist.");
        }
        String srcConfigSrc = srcRealm.getConfigFile().getAbsolutePath();

        File realmConfigFile = resolveFile(new File(srcConfigSrc), _realmsConfigFile);
        Configuration realmConfig = builder.buildFromFile(realmConfigFile);
        Configuration srcRootConfig = realmConfig.getChild("root-dir", false);
        if (srcRootConfig == null) {
            throw new Exception("cannot copy realm '" + srcRealmID + "' no root dir specified in config file");
        }
        File srcRootDir = new File(srcRootConfig.getValue());
        if (!srcRootDir.isAbsolute()) {
            srcRootDir = resolveFile(srcRootDir, realmConfigFile).getCanonicalFile();
        }

        // copy realm directory:
        File destRootDir;
        if (destDir != null) {
            if (!destDir.exists() || !destDir.isDirectory()) {
                if (!new File(destDir.getAbsolutePath()).mkdirs()) {
                    throw new Exception("cannot create directory: " + destDir);
                }
            }
            destRootDir = new File(destDir, destRealmID);
        } else {
            destRootDir = new File(srcRootDir.getParentFile(), destRealmID);
        }
        log.debug("copying realm " + srcRootDir + " to " + destRootDir);
        byte[] buffer = new byte[8192];
        String[] filter = { ".svn", ".cvs" };
        FileUtil.copyDirectory(srcRootDir, destRootDir, buffer, filter);

        // patch new realm:
        if (!srcConfigSrc.startsWith(srcRootDir.getAbsolutePath())) {
            throw new Exception(srcConfigSrc + " does not start with " + srcRootDir);
        }

        String configPath = srcConfigSrc.substring(srcRootDir.getAbsolutePath().length());
        if (!configPath.startsWith(File.separator)) {
            configPath = File.separator + configPath;
        }
        String destConfigSrc = destRootDir.getAbsolutePath() + configPath;
        log.debug("destConfigSrc: " + destConfigSrc);

        realmConfigFile = resolveFile(new File(destConfigSrc), _realmsConfigFile);
        realmConfig = builder.buildFromFile(realmConfigFile);
        Element realmDocument = ConfigurationUtil.toElement(realmConfig);

        Element nameElement = (Element) realmDocument.getElementsByTagName("name").item(0);
        Node text = realmDocument.getOwnerDocument().createTextNode(destRealmName);
        nameElement.removeChild(nameElement.getFirstChild());
        nameElement.appendChild(text);
        Element rootDirElement = (Element) realmDocument.getElementsByTagName("root-dir").item(0);
        text = realmDocument.getOwnerDocument().createTextNode(destRootDir.getAbsolutePath());
        rootDirElement.removeChild(rootDirElement.getFirstChild());
        rootDirElement.appendChild(text);

        realmConfig = ConfigurationUtil.toConfiguration(realmDocument);
        DefaultConfigurationSerializer configSerializer = new DefaultConfigurationSerializer();
        configSerializer.setIndent(true);
        configSerializer.serializeToFile(realmConfigFile, realmConfig);

        // add realm to realms.xml and register it:
        addRealm(destRealmID, destRealmName, destMountPoint, destConfigSrc);
    }

}