org.glassfish.admin.amx.impl.config.AMXConfigLoader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of payara-micro Show documentation
Show all versions of payara-micro Show documentation
Micro Distribution of the Payara Project
/*
* 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 extends ConfigBeanProxy> intf) {
// cache it
ConfigBeanJMXSupportRegistry.getInstance(intf);
}
public Map getConfiguredTypes() {
final List> classes = ConfigBeanJMXSupportRegistry.getConfiguredClasses();
final Map types = MapUtil.newMap();
for (final Class extends ConfigBeanProxy> 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 extends ConfigBeanProxy> 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;
}
}
}