All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.enioka.jqm.tools.JndiContext Maven / Gradle / Ivy

There is a newer version: 2.2.9
Show newest version
/**
 * Copyright © 2013 enioka. All rights reserved
 * Authors: Marc-Antoine GOUILLART ([email protected])
 *          Pierre COPPEE ([email protected])
 *
 * 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.enioka.jqm.tools;

import java.io.File;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.rmi.Remote;
import java.rmi.registry.Registry;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.naming.CompositeName;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NameParser;
import javax.naming.NamingException;
import javax.naming.spi.InitialContextFactory;
import javax.naming.spi.InitialContextFactoryBuilder;
import javax.naming.spi.NamingManager;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class implements a basic JNDI context
 * 
 */
class JndiContext extends InitialContext implements InitialContextFactoryBuilder, InitialContextFactory, NameParser
{
    private static Logger jqmlogger = LoggerFactory.getLogger(JndiContext.class);

    private Map singletons = new HashMap();
    private List jmxNames = new ArrayList();
    private Registry r = null;
    private ClassLoader extResources;

    /**
     * Will create a JNDI Context and register it as the initial context factory builder
     * 
     * @return the context
     * @throws NamingException
     *             on any issue during initial context factory builder registration
     */
    static JndiContext createJndiContext() throws NamingException
    {
        try
        {
            if (!NamingManager.hasInitialContextFactoryBuilder())
            {
                JndiContext ctx = new JndiContext();
                NamingManager.setInitialContextFactoryBuilder(ctx);
                return ctx;
            }
            else
            {
                return (JndiContext) NamingManager.getInitialContext(null);
            }
        }
        catch (Exception e)
        {
            jqmlogger.error("Could not create JNDI context: " + e.getMessage());
            NamingException ex = new NamingException("Could not initialize JNDI Context");
            ex.setRootCause(e);
            throw ex;
        }
    }

    /**
     * Create a new Context
     * 
     * @throws NamingException
     */
    private JndiContext() throws NamingException
    {
        super();

        // List all jars inside ext directory
        File extDir = new File("ext/");
        List urls = new ArrayList();
        if (extDir.isDirectory())
        {
            for (File f : FileUtils.listFiles(extDir, new String[] { "jar", "war", "bar" }, true))
            {
                if (!f.canRead())
                {
                    throw new NamingException("can't access file " + f.getAbsolutePath());
                }
                try
                {
                    urls.add(f.toURI().toURL());
                }
                catch (MalformedURLException e)
                {
                    jqmlogger.error("Error when parsing the content of ext directory. File will be ignored", e);
                }
            }

            // Create classloader
            final URL[] aUrls = urls.toArray(new URL[0]);
            for (URL u : aUrls)
            {
                jqmlogger.trace(u.toString());
            }
            extResources = AccessController.doPrivileged(new PrivilegedAction()
            {
                @Override
                public URLClassLoader run()
                {
                    return new URLClassLoader(aUrls, getParentCl());
                }
            });
        }
        else
        {
            throw new NamingException("JQM_ROOT/ext directory does not exist or cannot be read");
        }
    }

    @Override
    public Object lookup(String name) throws NamingException
    {
        if (name == null)
        {
            throw new IllegalArgumentException("name cannot be null");
        }
        jqmlogger.trace("Looking up a JNDI element named " + name);

        // Special delegated cases
        if (name.startsWith("rmi:"))
        {
            try
            {
                return this.r.lookup(name.split("/")[3]);
            }
            catch (Exception e)
            {
                NamingException e1 = new NamingException();
                e1.setRootCause(e);
                throw e1;
            }
        }
        if (name.endsWith("serverName"))
        {
            return JqmEngine.latestNodeStartedName;
        }

        // If in cache...
        if (singletons.containsKey(name))
        {
            jqmlogger.trace("JNDI element named " + name + " found in cache.");
            return singletons.get(name);
        }

        // Retrieve the resource description from the database or the XML file
        JndiResourceDescriptor d = ResourceParser.getDescriptor(name);
        jqmlogger.trace("JNDI element named " + name + " not found in cache. Will be created. Singleton status: " + d.isSingleton());

        // Singleton handling is synchronized to avoid double creation
        if (d.isSingleton())
        {
            synchronized (singletons)
            {
                if (singletons.containsKey(name))
                {
                    return singletons.get(name);
                }

                // We use the current thread loader to find the resource and resource factory class - ext is inside that CL.
                // This is done only for payload CL - engine only need ext, not its own CL (as its own CL does NOT include ext).
                Object res = null;
                try
                {
                    ResourceFactory rf = new ResourceFactory(
                            Thread.currentThread().getContextClassLoader() instanceof com.enioka.jqm.tools.JarClassLoader
                                    ? Thread.currentThread().getContextClassLoader()
                                    : extResources);
                    res = rf.getObjectInstance(d, null, this, new Hashtable());
                }
                catch (Exception e)
                {
                    jqmlogger.warn("Could not instanciate singleton JNDI object resource " + name, e);
                    NamingException ex = new NamingException(e.getMessage());
                    ex.initCause(e);
                    throw ex;
                }

                // Cache result
                if (res.getClass().getClassLoader() instanceof JarClassLoader)
                {
                    jqmlogger.warn(
                            "A JNDI resource was defined as singleton but was loaded by a payload class loader - it won't be cached to avoid class loader leaks");
                }
                else
                {
                    singletons.put(name, res);

                    // Pool JMX registration (only if cached - avoids leaks)
                    if ("org.apache.tomcat.jdbc.pool.DataSourceFactory".equals(d.getFactoryClassName())
                            && (d.get("jmxEnabled") == null ? true : Boolean.parseBoolean((String) d.get("jmxEnabled").getContent())))
                    {
                        try
                        {
                            MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
                            ObjectName jmxname = new ObjectName("com.enioka.jqm:type=JdbcPool,name=" + name);
                            mbs.registerMBean(res.getClass().getMethod("getPool").invoke(res).getClass().getMethod("getJmxPool")
                                    .invoke(res.getClass().getMethod("getPool").invoke(res)), jmxname);
                            jmxNames.add(jmxname);
                        }
                        catch (Exception e)
                        {
                            jqmlogger.warn("Could not register JMX MBean for resource.", e);
                        }
                    }
                }

                // Done
                return res;
            }
        }

        // Non singleton
        try
        {
            // We use the current thread loader to find the resource and resource factory class - ext is inside that CL.
            // This is done only for payload CL - engine only need ext, not its own CL (as its own CL does NOT include ext).
            ResourceFactory rf = new ResourceFactory(
                    Thread.currentThread().getContextClassLoader() instanceof com.enioka.jqm.tools.JarClassLoader
                            ? Thread.currentThread().getContextClassLoader()
                            : extResources);
            return rf.getObjectInstance(d, null, this, new Hashtable());
        }
        catch (Exception e)
        {
            jqmlogger.warn("Could not instanciate JNDI object resource " + name, e);
            NamingException ex = new NamingException(e.getMessage());
            ex.initCause(e);
            throw ex;
        }
    }

    void resetSingletons()
    {
        jqmlogger.info("Resetting singleton JNDI resource cache");
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        for (ObjectName n : this.jmxNames)
        {
            try
            {
                mbs.unregisterMBean(n);
            }
            catch (Exception e)
            {
                jqmlogger.error("could not unregister bean", e);
            }
        }
        this.jmxNames = new ArrayList();
        this.singletons = new HashMap();
    }

    @Override
    public Object lookup(Name name) throws NamingException
    {
        return this.lookup(StringUtils.join(Collections.list(name.getAll()), "/"));
    }

    @Override
    public Context getInitialContext(Hashtable environment) throws NamingException
    {
        return this;
    }

    @Override
    public InitialContextFactory createInitialContextFactory(Hashtable environment) throws NamingException
    {
        return this;
    }

    @Override
    public NameParser getNameParser(String name) throws NamingException
    {
        return this;
    }

    @Override
    public Name parse(String name) throws NamingException
    {
        return new CompositeName(name);
    }

    @Override
    public void close() throws NamingException
    {
        // Nothing to do.
    }

    @Override
    public void bind(String name, Object obj) throws NamingException
    {
        jqmlogger.debug("binding [" + name + "] to a [" + obj.getClass().getCanonicalName() + "]");
        if (r != null && name.startsWith("rmi://"))
        {
            try
            {
                jqmlogger.debug("binding [" + name.split("/")[3] + "] to a [" + obj.getClass().getCanonicalName() + "]");
                this.r.bind(name.split("/")[3], (Remote) obj);
            }
            catch (Exception e)
            {
                NamingException e1 = new NamingException("could not bind RMI object");
                e1.setRootCause(e);
                throw e1;
            }
        }
        else
        {
            this.singletons.put(name, obj);
        }
    }

    @Override
    public void bind(Name name, Object obj) throws NamingException
    {
        this.bind(StringUtils.join(Collections.list(name.getAll()), "/"), obj);
    }

    /**
     * Will register the given Registry as a provider for the RMI: context. If there is already a registered Registry, the call is ignored.
     * 
     * @param r
     */
    void registerRmiContext(Registry r)
    {
        if (this.r == null)
        {
            this.r = r;
        }
    }

    /**
     * @return the class loader holding the ext directory (or null if no ext directory - should never happen)
     */
    ClassLoader getExtCl()
    {
        return this.extResources;
    }

    @Override
    public void unbind(Name name) throws NamingException
    {
        this.unbind(StringUtils.join(Collections.list(name.getAll()), "/"));
    }

    @Override
    public void unbind(String name) throws NamingException
    {
        if (r != null && name.startsWith("rmi://"))
        {
            try
            {
                jqmlogger.debug("unbinding RMI name " + name);
                this.r.unbind(name.split("/")[3]);
            }
            catch (Exception e)
            {
                NamingException e1 = new NamingException("could not unbind RMI name");
                e1.setRootCause(e);
                throw e1;
            }
        }
        else
        {
            this.singletons.remove(name);
        }
    }

    /**
     * A helper - in Java 9, the extension CL was renamed to platform CL and hosts all the JDK classes. Before 9, it was useless and we used
     * bootstrap CL instead.
     *
     * @return the base CL to use.
     */
    private static ClassLoader getParentCl()
    {
        try
        {
            Method m = ClassLoader.class.getMethod("getPlatformClassLoader");
            return (ClassLoader) m.invoke(null);
        }
        catch (NoSuchMethodException e)
        {
            // Java < 9, just use the bootstrap CL.
            return null;
        }
        catch (Exception e)
        {
            throw new JqmInitError("Could not fetch Platform Class Loader", e);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy