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

org.glassfish.admin.amx.impl.config.AMXConfigLoader Maven / Gradle / Ivy

There is a newer version: 7.2024.1.Alpha1
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2008-2012 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
 
 // Portions Copyright [2016-2019] [Payara Foundation and/or its affiliates]
 
package org.glassfish.admin.amx.impl.config;

import com.sun.enterprise.config.serverbeans.Domain;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.management.*;
import org.glassfish.admin.amx.config.AMXConfigConstants;
import org.glassfish.admin.amx.core.Util;
import org.glassfish.admin.amx.impl.util.ImplUtil;
import org.glassfish.admin.amx.impl.util.InjectedValues;
import org.glassfish.admin.amx.impl.util.ObjectNameBuilder;
import org.glassfish.admin.amx.impl.util.SingletonEnforcer;
import org.glassfish.admin.amx.util.AMXLoggerInfo;
import org.glassfish.admin.amx.util.ExceptionUtil;
import org.glassfish.admin.amx.util.FeatureAvailability;
import org.glassfish.admin.amx.util.MapUtil;
import org.glassfish.admin.amx.util.TypeCast;
import org.glassfish.admin.mbeanserver.PendingConfigBeanJob;
import org.glassfish.admin.mbeanserver.PendingConfigBeans;
import org.glassfish.external.amx.AMXGlassfish;
import org.glassfish.external.arc.Stability;
import org.glassfish.external.arc.Taxonomy;
import org.jvnet.hk2.config.*;

/**
 * Responsible for loading ConfigBeanProxy MBeans (com.sun.enterprise.config.serverbeans.*)
 * @author llc
 */
@Taxonomy(stability = Stability.NOT_AN_INTERFACE)
public final class AMXConfigLoader implements TransactionListener {

    private static final Logger mLogger = AMXLoggerInfo.getLogger();
    
    private volatile AMXConfigLoaderThread mLoaderThread;
    private final Transactions mTransactions;
    private final PendingConfigBeans mPendingConfigBeans;
    private final ConfigBeanRegistry mRegistry = ConfigBeanRegistry.getInstance();
    private final MBeanServer mServer;
    
    /**
     * Detects illegal characters in MBean name values.  (Used instead of
     * individual character searches for efficiency)
     */
    private static final Pattern ILLEGAL_JMX_NAME_PATTERN = Pattern.compile(".*[=:"
            + Pattern.quote("\"") + Pattern.quote("*") + Pattern.quote("?") + "].*");
    
    public AMXConfigLoader(
            final MBeanServer mbeanServer,
            final PendingConfigBeans pending,
            final Transactions transactions) {
        if (transactions == null) {
            throw new IllegalStateException("AMXConfigLoader.AMXConfigLoader: null Transactions");
        }

        mServer = mbeanServer;
        mTransactions = transactions;
        mPendingConfigBeans = pending;
    }

    public void registerConfigured(final Class intf) {
        // cache it
        ConfigBeanJMXSupportRegistry.getInstance(intf);
    }

    public Map getConfiguredTypes() {
        final List> classes = ConfigBeanJMXSupportRegistry.getConfiguredClasses();
        final Map types = MapUtil.newMap();

        for (final Class clazz : classes) {
            final String classname = clazz.getName();
            final String elementType = Util.typeFromName(classname);
            types.put(elementType, classname);
        }
        return types;
    }

    private void configBeanRemoved(final ConfigBean cb) {
        final ObjectName objectName = mRegistry.getObjectName(cb);
        if (objectName != null) {
            ImplUtil.unregisterAMXMBeans(mServer, objectName);
            mRegistry.remove(objectName);
        } else {
            // might or might not be there, but make sure it's gone!
            mPendingConfigBeans.remove(cb);
        }
    }

    private void issueAttributeChange(
            final ConfigBean cb,
            final String xmlAttrName,
            final Object oldValue,
            final Object newValue,
            final long whenChanged) {
        final ObjectName objectName = mRegistry.getObjectName(cb);
        if (objectName == null) {
            throw new IllegalArgumentException("Can't issue attribute change for null ObjectName for ConfigBean " + cb.getProxyType().getName());
        }

        boolean changed = false;
        if (oldValue != null) {
            changed = !oldValue.equals(newValue);
        } else if (newValue != null) {
            changed = true;
        }

        if (changed) {
            final Object impl = mRegistry.getImpl(cb);
            if (!(impl instanceof AMXConfigImpl)) {
                throw new IllegalStateException("impossible");
            }
            final AMXConfigImpl amx = (AMXConfigImpl) impl;
            final String message = cb.getProxyType().getName() + "." + xmlAttrName + ": " + oldValue + " => " + newValue;
            amx.issueAttributeChangeForXmlAttrName(xmlAttrName, message, oldValue, newValue, whenChanged);
        }
    }

    private void sortAndDispatch(
            final List events,
            final long whenChanged) {
        final List newConfigBeans = new ArrayList();
        final List remainingEvents = new ArrayList();

        //
        // Process all ADD and REMOVE events first, placing leftovers into 'remainingEvents'
        // We do this even if AMX is *not* running, because they new ConfigBeans need to go
        // into the queue for when and if AMX starts running.
        // 
        for (final PropertyChangeEvent event : events) {
            final Object oldValue = event.getOldValue();
            final Object newValue = event.getNewValue();
            
            if (oldValue == null && newValue instanceof ConfigBeanProxy) {
                // ADD: a new ConfigBean was added
                final ConfigBeanProxy cbp = (ConfigBeanProxy) newValue;
                final ConfigBean cb = asConfigBean(ConfigBean.unwrap(cbp));
                final boolean doWait = amxIsRunning();
                if (handleConfigBean(cb, doWait)) // wait until registered
                {
                    newConfigBeans.add(cb);
                }
            } else if (newValue == null && oldValue instanceof ConfigBeanProxy && amxIsRunning()) {
                // REMOVE
                final ConfigBeanProxy cbp = (ConfigBeanProxy) oldValue;
                final ConfigBean cb = asConfigBean(ConfigBean.unwrap(cbp));
                configBeanRemoved(cb);
            } else {
                remainingEvents.add(event);
            }
        }

        // we can't issue events if AMX is not running!
        if (amxIsRunning()) {
            for (final PropertyChangeEvent event : remainingEvents) {
                final Object oldValue = event.getOldValue();
                final Object newValue = event.getNewValue();
                final Object source = event.getSource();
                final String propertyName = event.getPropertyName();
                if (source instanceof ConfigBeanProxy) {
                    // CHANGE
                    final ConfigBeanProxy cbp = (ConfigBeanProxy) source;
                    final ConfigBean cb = asConfigBean(ConfigBean.unwrap(cbp));

                    // change events without prior add
                    // we shouldn't have to check for this, but it's a bug in the caller: no even for
                    // new ConfigBean, but changes come along anyway

                    if (mRegistry.getObjectName(cb) == null) {
                        if (!newConfigBeans.contains(cb) && handleConfigBean(cb, false)) {
                                newConfigBeans.add(cb);
                        }
                    } else {
                        issueAttributeChange(cb, propertyName, oldValue, newValue, whenChanged);
                    }
                } else {
                    mLogger.log(Level.WARNING, "AMXConfigLoader.sortAndDispatch: source is not a ConfigBean");
                }
            }
        }
    }

    @Override
    public void transactionCommited(final List changes) {
        sortAndDispatch(changes, System.currentTimeMillis());
    }

    @Override
    public void unprocessedTransactedEvents(List changes) {
        // not interested...
    }

    public void handleNotification(final Notification notif, final Object handback) {
    }

    public void stop() {
        mTransactions.removeTransactionsListener(this);
        SingletonEnforcer.deregister(AMXConfigLoader.class, this);
    }

    /**
    No items will be processd until {@link #start} is called.
     */
    boolean handleConfigBean(final ConfigBean cb, final boolean waitDone) {
        boolean processed = true;

        if (mRegistry.getObjectName(cb) == null) {
            final PendingConfigBeanJob job = mPendingConfigBeans.add(cb, waitDone);

            // a job could come back null for a bogus ConfigBean
            if (job == null) {
                mLogger.log(Level.INFO, AMXLoggerInfo.configBeanNotProcessed, cb.getProxyType().getName());
                processed = false;
            } else if (waitDone) {
                try {
                    job.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        } else {
            // ok
        }
        return processed;
    }

    /**
     */
    private ConfigBean getActualParent(final ConfigBean configBean) {
        ConfigBean parent = asConfigBean(configBean.parent());
        if (parent == null && !configBean.getProxyType().getName().endsWith("Domain")) {
                throw new IllegalStateException("WARNING: parent is null for " + configBean.getProxyType().getName() + ",  see issue #10528");
        }

        return parent;
    }

    private ObjectName getActualParentObjectName(final ConfigBean configBean) {
        ObjectName parentObjectName = null;

        final ConfigBean parent = getActualParent(configBean);
        if (parent != null) {
            parentObjectName = mRegistry.getObjectName(parent);
        }

        return parentObjectName;
    }

    /**
    @return a ConfigBean, or null if it's not a ConfigBean
     */
    @SuppressWarnings("unchecked")
    static ConfigBean asConfigBean(final Object o) {
        if (o == null) {
            return null;
        }

        if (!(o instanceof ConfigBean)) {
            throw new IllegalArgumentException("Not a ConfigBean: " + o.getClass().getName());
        }

        return (ConfigBean) o;
    }

    /**
    Enable registration of MBeans, queued until now.
     */
    public synchronized ObjectName start() {
        mLogger.log(Level.INFO,AMXLoggerInfo.inAMXConfigLoader, mLoaderThread );
        if (mLoaderThread == null) {
            FeatureAvailability.getInstance().waitForFeature(FeatureAvailability.AMX_CORE_READY_FEATURE, "AMXConfigLoader.start");

            mLoaderThread = new AMXConfigLoaderThread(mPendingConfigBeans);
            mLoaderThread.setDaemon(true);
            mLoaderThread.setName("AMX Config Loader");
            mLoaderThread.start();

            mPendingConfigBeans.swapTransactionListener(this);
            SingletonEnforcer.register(AMXConfigLoader.class, this);

            // wait until config beans have been loaded as MBeans
            mLoaderThread.waitInitialQueue();

            // Now the Config subsystem is ready: after the first queue of ConfigBeans are registered as MBeans
            // and after the above MBeans are registered.
            final ObjectName domainObjectName = ConfigBeanRegistry.getInstance().getObjectNameForProxy(getDomain());
            mLogger.log(Level.INFO,"AMX Config Loader: Domain started: `{0}`.", domainObjectName);
            FeatureAvailability.getInstance().registerFeature(AMXConfigConstants.AMX_CONFIG_READY_FEATURE, domainObjectName);
        }
        return null;
    }

    private synchronized boolean amxIsRunning() {
        return mLoaderThread != null;
    }

    private final class AMXConfigLoaderThread extends Thread {

        private final PendingConfigBeans mPending;
        volatile boolean mQuit = false;
        private volatile CountDownLatch mInitalQueueLatch = new CountDownLatch(1);

        AMXConfigLoaderThread(final PendingConfigBeans pending) {
            super("AMXConfigLoader.AMXConfigLoaderThread");
            mPending = pending;
        }

        void quit() {
            mQuit = true;
        }

        private ObjectName registerOne(final PendingConfigBeanJob job) {
            final ConfigBean cb = job.getConfigBean();

            ObjectName objectName = mRegistry.getObjectName(cb);
            try {
                // If the ObjectName is null, then it hasn't been registered
                // Due to recursive registration of parents, we could encounter beans
                // that are parents, and thus already registered.
                if (objectName == null) {
                    objectName = registerConfigBeanAsMBean(cb);
                }
            } catch (final Throwable t) {
                mLogger.log(Level.WARNING, AMXLoggerInfo.cantRegister, new Object[]{getType(cb), getKey(cb), t});
            } finally {
                job.releaseLatch();
            }

            return objectName;
        }

        @Override
        public void run() {
            try {
                doRun();
            } catch (final Throwable t) {
                mLogger.log(Level.SEVERE, AMXLoggerInfo.unexpectedDeath, t);
            }
        }

        /** wait until the initial queue of MBeans has been processed */
        public void waitInitialQueue() {
            final CountDownLatch latch = mInitalQueueLatch;
            if (latch != null) {
                try {
                    latch.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                mInitalQueueLatch = null;
            }
        }

        protected void doRun() throws Exception {
            /*
            First pass *only*:
            Note when we initially empty the queue; this signifies that
            AMX is "ready" for callers that just started it.
             */
            PendingConfigBeanJob job = mPending.take();  // block until first item is ready
            while ((!mQuit) && job != null) {
                registerOne(job);
                job = mPending.peek();  // don't block, loop exits when queue is first emptied
                if (job != null) {
                    job = mPending.take();
                }
            }
            mInitalQueueLatch.countDown();

            // ongoing processing once initial queue has been emptied: blocking behavior
            while (!mQuit) {
                job = mPending.take();
                registerOne(job);
            }
        }
    }

    /**
    Register the ConfigBean, first registering its parent, parent's parent, etc if not
    already present.
     */
    private ObjectName registerConfigBeanAsMBean(final ConfigBean cb) {
        ObjectName objectName = null;

        final ConfigBean parentCB = getActualParent(cb);
        if (parentCB != null && mRegistry.getObjectName(parentCB) == null) {
            registerConfigBeanAsMBean(parentCB);
        }
        objectName = _registerConfigBeanAsMBean(cb, parentCB);
        assert objectName == null || mRegistry.getObjectName(cb) != null;
        return objectName;
    }

    /**
    Parent must have been registered already.
     */
    private ObjectName _registerConfigBeanAsMBean(
            final ConfigBean cb,
            final ConfigBean parentCB) {
        final Class cbClass = cb.getProxyType();


        ObjectName objectName = mRegistry.getObjectName(cb);
        if (objectName != null) {
            throw new IllegalArgumentException("ConfigBean " + cbClass.getName() + " already registered as " + objectName);
        }
        if (parentCB != null && mRegistry.getObjectName(parentCB) == null) {
            throw new IllegalArgumentException("ConfigBean parent " + parentCB.getProxyType().getName()
                    + " must be registered first before child = " + cbClass.getName());
        }

        objectName = buildObjectName(cb);

        objectName = createAndRegister(cb, objectName);
        if (objectName != null) {
            mLogger.log(Level.FINE, "REGISTERED MBEAN: {0}", objectName);
        }

        return objectName;
    }

    private ObjectName createAndRegister(
            final ConfigBean cb,
            final ObjectName objectNameIn) {
        ObjectName objectName;

        ObjectName parentObjectName = getActualParentObjectName(cb);

        if (parentObjectName == null) {
            parentObjectName = AMXGlassfish.DEFAULT.domainRoot();
        }

        final AMXConfigImpl impl = new AMXConfigImpl(parentObjectName, cb);

        try {
            final ObjectInstance instance = mServer.registerMBean(impl, objectNameIn);
            objectName = instance.getObjectName();
            mRegistry.add(cb, objectName, impl);
        } catch (InstanceAlreadyExistsException ex) {
            mLogger.log(Level.FINE, ExceptionUtil.toString(ex));
            objectName = null;
        } catch (final JMException e) {
            mLogger.log(Level.SEVERE, ExceptionUtil.toString(e));
            objectName = null;
        } 
        return objectName;
    }

    private String getType(final ConfigBean cb) {
        final ConfigBeanJMXSupport spt = ConfigBeanJMXSupportRegistry.getInstance(cb);
        return spt.getTypeString();
    }

    /** Get the key value eg the name to be used in an ObjectName */
    static String getKey(final ConfigBean cb) {
        final ConfigBeanJMXSupport spt = ConfigBeanJMXSupportRegistry.getInstance(cb);

        if (spt.isSingleton()) {
            return null;
        }

        String name = null;

        final String nameHint = spt.getNameHint();

        if (nameHint == null) {
            name = "MISSING_NAME__KEY_MUST_BE_SPECIFIED_IN_INTERFACE";
        } else if (spt.nameHintIsElement()) {
            final List leaf = cb.leafElements(nameHint);
            if (leaf != null) {
                // verify that it is List -- no other types are supported in this way
                final List items = TypeCast.checkList(leaf, String.class);
                if (items.size() != 1) {
                    throw new IllegalArgumentException("Can't find sub-element of type " + nameHint + " in " + cb.getProxyType().getName());
                }
                name = items.get(0);
            }
        } else {
            name = cb.rawAttribute(nameHint);
        }

        return name;
    }

    public Domain getDomain() {
    	return InjectedValues.getInstance().getHabitat().getService(Domain.class);
    }

    private static final AtomicLong sCounter = new AtomicLong(1);

    private ObjectName buildObjectName(final ConfigBean cb) {
        ObjectName parentObjectName;
        final ConfigBean parent = getActualParent(cb);

        if (parent == null) {
            parentObjectName = AMXGlassfish.DEFAULT.domainRoot();
        } else {
            parentObjectName = mRegistry.getObjectName(parent);
        }

        final String type = getType(cb);
        String name = getKey(cb);

        final ConfigBeanJMXSupport spt = ConfigBeanJMXSupportRegistry.getInstance(cb);
        if ((!spt.isSingleton()) && (name == null || name.length() == 0)) {
            name = "MISSING_NAME-" + sCounter.getAndIncrement();
            mLogger.log(Level.WARNING, AMXLoggerInfo.nonsingletonConfigbean, new Object[] {cb.getProxyType().getName() ,name});
        }

        return ObjectNameBuilder.buildChildObjectName(mServer, parentObjectName, type, quoteIfNeeded(name));
    }
    
    /**
     * Quotes the name string if it contains any characters that are illegal
     * in MBean names.
     * 
     * @param name the string to examine
     * @return quoted string if it contains illegal characters; the string otherwise
     */
    private static String quoteIfNeeded(final String name) {
        /*
         * JMX names cannot include =  or , or : or * or ? unless they are part of
         * the value and they are quoted.
         */
        
        if (name != null && ILLEGAL_JMX_NAME_PATTERN.matcher(name).matches()) {
            return "\"" + name + "\"";
        } else {
            return name;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy