/*
* JBoss, Home of Professional Open Source
* Copyright 2008, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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 2.1 of
* the License, or (at your option) any later version.
*
* This software 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.threads.metadata;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Collections;
import java.util.Queue;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import static java.lang.Math.max;
import java.lang.annotation.Annotation;
import org.jboss.xb.annotations.JBossXmlSchema;
import org.jboss.beans.metadata.spi.BeanMetaData;
import org.jboss.beans.metadata.spi.ValueMetaData;
import org.jboss.beans.metadata.spi.BeanMetaDataFactory;
import org.jboss.beans.metadata.spi.builder.BeanMetaDataBuilder;
import org.jboss.threads.JBossThreadFactory;
import org.jboss.threads.InterruptHandler;
import org.jboss.threads.SimpleQueueExecutor;
import org.jboss.threads.JBossExecutors;
import org.jboss.threads.RejectionPolicy;
import org.jboss.threads.ArrayQueue;
import org.jboss.threads.JBossThreadPoolExecutor;
import org.jboss.threads.DirectExecutor;
import org.jboss.threads.ThreadPoolExecutorMBean;
import org.jboss.dependency.spi.ControllerMode;
import org.jboss.aop.microcontainer.aspects.jmx.JMX;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlTransient;
/**
*
*/
@JBossXmlSchema(namespace = "urn:jboss:threads:1.0", elementFormDefault = XmlNsForm.QUALIFIED)
@XmlRootElement(name = "threads")
@XmlType(name = "threads")
public final class ThreadsMetaData implements BeanMetaDataFactory {
private static final class StringEntry implements Map.Entry<String, String> {
private final String key;
private final String value;
private StringEntry(final String key, final String value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
public String setValue(final String value) {
throw new UnsupportedOperationException();
}
}
private static StringEntry entry(final String key, final String value) {
return new StringEntry(key, value);
}
private static Map<String, String> stringMap(StringEntry... entries) {
final HashMap<String, String> hashMap = new HashMap<String, String>(entries.length);
for (Map.Entry<String, String> e : entries) {
hashMap.put(e.getKey(), e.getValue());
}
return Collections.unmodifiableMap(hashMap);
}
private static final Map<String, String> UNIT_NICK_NAMES = stringMap(
entry("S", "SECONDS"),
entry("SEC", "SECONDS"),
entry("M", "MINUTES"),
entry("MIN", "MINUTES"),
entry("MS", "MILLISECONDS"),
entry("NS", "NANOSECONDS"),
entry("H", "HOURS"),
entry("D", "DAYS"),
entry("MON", "MONTHS"),
entry("W", "WEEKS")
);
private List<ThreadGroupMetaData> threadGroups = new ArrayList<ThreadGroupMetaData>();
private List<ThreadFactoryMetaData> threadFactories = new ArrayList<ThreadFactoryMetaData>();
private List<ThreadPoolExecutorMetaData> threadPoolExecutors = new ArrayList<ThreadPoolExecutorMetaData>();
private List<DirectExecutorMetaData> directExecutors = new ArrayList<DirectExecutorMetaData>();
private List<NotatingExecutorMetaData> notatingExecutors = new ArrayList<NotatingExecutorMetaData>();
private List<ScheduledThreadPoolExecutorMetaData> scheduledThreadPoolExecutors = new ArrayList<ScheduledThreadPoolExecutorMetaData>();
public List<ThreadGroupMetaData> getThreadGroups() {
return threadGroups;
}
@XmlElement(name = "thread-group")
public void setThreadGroups(final List<ThreadGroupMetaData> threadGroups) {
this.threadGroups = threadGroups;
}
public List<ThreadFactoryMetaData> getThreadFactories() {
return threadFactories;
}
@XmlElement(name = "thread-factory")
public void setThreadFactories(final List<ThreadFactoryMetaData> threadFactories) {
this.threadFactories = threadFactories;
}
public List<ThreadPoolExecutorMetaData> getThreadPoolExecutors() {
return threadPoolExecutors;
}
@XmlElement(name = "thread-pool-executor")
public void setThreadPoolExecutors(final List<ThreadPoolExecutorMetaData> threadPoolExecutors) {
this.threadPoolExecutors = threadPoolExecutors;
}
public List<DirectExecutorMetaData> getDirectExecutors() {
return directExecutors;
}
@XmlElement(name = "direct-executor")
public void setDirectExecutors(final List<DirectExecutorMetaData> directExecutors) {
this.directExecutors = directExecutors;
}
public List<NotatingExecutorMetaData> getNotatingExecutors() {
return notatingExecutors;
}
@XmlElement(name = "notating-executor")
public void setNotatingExecutors(final List<NotatingExecutorMetaData> notatingExecutors) {
this.notatingExecutors = notatingExecutors;
}
public List<ScheduledThreadPoolExecutorMetaData> getScheduledThreadPoolExecutors() {
return scheduledThreadPoolExecutors;
}
@XmlElement(name = "scheduled-thread-pool-executor")
public void setScheduledThreadPoolExecutors(final List<ScheduledThreadPoolExecutorMetaData> scheduledThreadPoolExecutors) {
this.scheduledThreadPoolExecutors = scheduledThreadPoolExecutors;
}
@XmlTransient
public List<BeanMetaData> getBeans() {
List<BeanMetaData> beanMetaDataList = new ArrayList<BeanMetaData>();
for (ThreadGroupMetaData metaData : threadGroups) {
final String name = metaData.getName();
final BeanMetaDataBuilder builder = BeanMetaDataBuilder.createBuilder(name, ThreadGroup.class.getName());
builder.setMode(ControllerMode.ON_DEMAND);
final String parent = metaData.getParent();
if (parent != null && parent.length() > 0) {
builder.addConstructorParameter(ThreadGroup.class.getName(), builder.createInject(parent));
}
builder.addConstructorParameter(String.class.getName(), builder.createValue(name));
if (metaData.isDaemon() != null) {
builder.addPropertyMetaData("daemon", builder.createValue(metaData.isDaemon()));
}
final Integer maxPriorityMeta = metaData.getMaxPriority();
if (maxPriorityMeta != null) {
builder.addPropertyMetaData("maxPriority", builder.createValue(maxPriorityMeta));
}
builder.ignoreStop();
builder.ignoreDestroy();
beanMetaDataList.add(builder.getBeanMetaData());
}
for (ThreadFactoryMetaData metaData : threadFactories) {
final String name = metaData.getName();
final BeanMetaDataBuilder builder = BeanMetaDataBuilder.createBuilder(name, JBossThreadFactory.class.getName());
builder.setMode(ControllerMode.ON_DEMAND);
final String group = metaData.getGroup();
builder.addConstructorParameter(ThreadGroup.class.getName(), group == null ? builder.createNull() : builder.createInject(group));
final Boolean daemon = metaData.getDaemon();
builder.addConstructorParameter(Boolean.class.getName(), daemon == null ? builder.createNull() : builder.createValue(daemon));
final Integer priorityMeta = metaData.getInitialPriority();
final Integer actualPriorityMeta;
if (priorityMeta != null) {
actualPriorityMeta = priorityMeta;
} else {
actualPriorityMeta = null;
}
builder.addConstructorParameter(Integer.class.getName(), actualPriorityMeta == null ? builder.createNull() : builder.createValue(actualPriorityMeta));
builder.addConstructorParameter(String.class.getName(), builder.createValue(metaData.getThreadNamePattern()));
final List<ValueMetaData> interruptHandlers = builder.createArray(InterruptHandler[].class.getName(), InterruptHandler.class.getName());
for (InterruptHandlerRefMetaData ihmd : metaData.getInterruptHandlers()) {
interruptHandlers.add(builder.createInject(ihmd.getName()));
}
builder.addConstructorParameter(InterruptHandler[].class.getName(), (ValueMetaData) interruptHandlers);
final ExceptionHandlerRefMetaData ehmd = metaData.getExceptionHandler();
builder.addConstructorParameter(Thread.UncaughtExceptionHandler.class.getName(), ehmd == null ? builder.createNull() : builder.createInject(ehmd.getName()));
final Long stackSize = metaData.getStackSize();
builder.addConstructorParameter(Long.class.getName(), stackSize == null ? builder.createNull() : builder.createValue(stackSize));
beanMetaDataList.add(builder.getBeanMetaData());
}
for (ThreadPoolExecutorMetaData metaData : threadPoolExecutors) {
final String name = metaData.getName();
final PoolSizeMetaData corePoolSizeMetaData = metaData.getCorePoolSize();
final int corePoolSize;
if (corePoolSizeMetaData == null) {
corePoolSize = 0;
} else {
corePoolSize = max(calcPoolSize(corePoolSizeMetaData), 0);
}
final PoolSizeMetaData maxPoolSizeMetaData = metaData.getMaxPoolSize();
final int maxPoolSize;
if (maxPoolSizeMetaData == null) {
maxPoolSize = max(corePoolSize, 1);
} else {
maxPoolSize = max(calcPoolSize(maxPoolSizeMetaData), max(1, corePoolSize));
}
final TimeMetaData timeMetaData = metaData.getKeepAliveTime();
final long time;
final TimeUnit unit;
if (timeMetaData == null) {
time = Long.MAX_VALUE;
unit = TimeUnit.NANOSECONDS;
} else {
time = max(0L, timeMetaData.getTime());
final String unitName = timeMetaData.getUnit();
if (unitName == null) {
unit = TimeUnit.MILLISECONDS;
} else {
final String upperUnitName = unitName.toUpperCase();
if (UNIT_NICK_NAMES.containsKey(upperUnitName)) {
unit = TimeUnit.valueOf(UNIT_NICK_NAMES.get(upperUnitName));
} else {
unit = TimeUnit.valueOf(upperUnitName);
}
}
}
final String threadFactory = metaData.getThreadFactory();
if (threadFactory == null) {
throw new IllegalArgumentException("threadFactory is not defined");
}
final Integer queueLength = metaData.getQueueLength();
final RejectPolicyMetaData rejectPolicyMetaData = metaData.getRejectPolicyMetaData();
final String policyName = rejectPolicyMetaData == null ? "block" : rejectPolicyMetaData.getName();
final BeanMetaDataBuilder executorBuilder;
// here is where we decide which thread pool implementation to use
// right now, our criteria is simple - if blocking is desired or if core threads can time out, use the queue executor instead
if (metaData.isAllowCoreTimeout() || "block".equals(policyName)) {
// use SimpleQueueExecutor
executorBuilder = BeanMetaDataBuilder.createBuilder(SimpleQueueExecutor.class.getName());
final RejectionPolicy rejectionPolicy;
final ValueMetaData handoffExecutorValue;
if ("abort".equals(policyName)) {
rejectionPolicy = RejectionPolicy.ABORT;
handoffExecutorValue = executorBuilder.createNull();
} else if ("block".equals(policyName)) {
rejectionPolicy = RejectionPolicy.BLOCK;
handoffExecutorValue = executorBuilder.createNull();
} else if ("caller-runs".equals(policyName)) {
rejectionPolicy = RejectionPolicy.HANDOFF;
handoffExecutorValue = executorBuilder.createValue(JBossExecutors.directExecutor());
} else if ("discard".equals(policyName)) {
rejectionPolicy = RejectionPolicy.DISCARD;
handoffExecutorValue = executorBuilder.createNull();
} else if ("discard-oldest".equals(policyName)) {
rejectionPolicy = RejectionPolicy.DISCARD_OLDEST;
handoffExecutorValue = executorBuilder.createNull();
} else if ("handoff".equals(policyName)) {
rejectionPolicy = RejectionPolicy.HANDOFF;
handoffExecutorValue = executorBuilder.createInject(rejectPolicyMetaData.getExecutorName());
} else {
throw new IllegalStateException();
}
final Queue<Runnable> queue;
if (queueLength == null) {
queue = new LinkedList<Runnable>();
} else {
queue = new ArrayQueue<Runnable>(queueLength.intValue());
}
executorBuilder.addConstructorParameter(String.class.getName(), name);
executorBuilder.addConstructorParameter("int", Integer.valueOf(corePoolSize));
executorBuilder.addConstructorParameter("int", Integer.valueOf(maxPoolSize));
executorBuilder.addConstructorParameter("long", Long.valueOf(time));
executorBuilder.addConstructorParameter(TimeUnit.class.getName(), unit);
executorBuilder.addConstructorParameter(Queue.class.getName(), executorBuilder.createValue(queue));
executorBuilder.addConstructorParameter(ThreadFactory.class.getName(), executorBuilder.createInject(threadFactory));
executorBuilder.addConstructorParameter(RejectionPolicy.class.getName(), rejectionPolicy);
executorBuilder.addConstructorParameter(Executor.class.getName(), handoffExecutorValue);
if (metaData.isAllowCoreTimeout()) {
executorBuilder.addPropertyMetaData("allowCoreTimeout", Boolean.TRUE);
}
} else {
// use ThreadPoolExecutor
executorBuilder = BeanMetaDataBuilder.createBuilder(JBossThreadPoolExecutor.class.getName());
final ValueMetaData policyValue;
if ("abort".equals(policyName)) {
policyValue = executorBuilder.createValue(JBossExecutors.abortPolicy());
} else if ("block".equals(policyName)) {
throw new IllegalStateException();
} else if ("caller-runs".equals(policyName)) {
policyValue = executorBuilder.createValue(JBossExecutors.callerRunsPolicy());
} else if ("discard".equals(policyName)) {
policyValue = executorBuilder.createValue(JBossExecutors.discardPolicy());
} else if ("discard-oldest".equals(policyName)) {
policyValue = executorBuilder.createValue(JBossExecutors.discardOldestPolicy());
} else if ("handoff".equals(policyName)) {
final BeanMetaDataBuilder policyBuilder = BeanMetaDataBuilder.createBuilder(RejectedExecutionHandler.class.getName());
policyBuilder.setFactoryClass(JBossExecutors.class.getName());
policyBuilder.setFactoryMethod("handoffPolicy");
policyBuilder.addConstructorParameter(Executor.class.getName(), policyBuilder.createInject(rejectPolicyMetaData.getExecutorName()));
policyValue = policyBuilder.getBeanMetaData();
} else {
throw new IllegalStateException();
}
final BlockingQueue<Runnable> queue;
if (queueLength == null) {
// todo: try LinkedTransferQueue
queue = new LinkedBlockingQueue<Runnable>();
} else {
queue = new ArrayBlockingQueue<Runnable>(queueLength.intValue());
}
executorBuilder.addConstructorParameter(String.class.getName(), name);
executorBuilder.addConstructorParameter("int", Integer.valueOf(corePoolSize));
executorBuilder.addConstructorParameter("int", Integer.valueOf(maxPoolSize));
executorBuilder.addConstructorParameter("long", Long.valueOf(time));
executorBuilder.addConstructorParameter(TimeUnit.class.getName(), unit);
executorBuilder.addConstructorParameter(BlockingQueue.class.getName(), executorBuilder.createValue(queue));
executorBuilder.addConstructorParameter(ThreadFactory.class.getName(), executorBuilder.createInject(threadFactory));
executorBuilder.addConstructorParameter(RejectedExecutionHandler.class.getName(), policyValue);
}
executorBuilder.addAnnotation(new JMX() {
public Class<?> exposedInterface() {
return ThreadPoolExecutorMBean.class;
}
public String name() {
return "jboss.threads:service=ThreadPoolExecutor,name=" + name;
}
public boolean registerDirectly() {
return false;
}
public Class<? extends Annotation> annotationType() {
return JMX.class;
}
});
executorBuilder.setMode(ControllerMode.ON_DEMAND);
// final BeanMetaDataBuilder builder = BeanMetaDataBuilder.createBuilder(name, ExecutorService.class.getName());
// builder.setFactoryClass(JBossExecutors.class.getName());
// builder.setFactoryMethod("protectedExecutorService");
// builder.addConstructorParameter(Executor.class.getName(), executorBuilder.getBeanMetaData());
// builder.setMode(ControllerMode.ON_DEMAND);
// beanMetaDataList.add(builder.getBeanMetaData());
executorBuilder.setName(name);
beanMetaDataList.add(executorBuilder.getBeanMetaData());
}
for (DirectExecutorMetaData metaData : directExecutors) {
final String name = metaData.getName();
final BeanMetaDataBuilder builder = BeanMetaDataBuilder.createBuilder(name, ExecutorService.class.getName());
builder.setFactoryClass(JBossExecutors.class.getName());
builder.setFactoryMethod("directExecutorService");
builder.setMode(ControllerMode.ON_DEMAND);
beanMetaDataList.add(builder.getBeanMetaData());
}
for (NotatingExecutorMetaData metaData : notatingExecutors) {
final String name = metaData.getName();
final BeanMetaDataBuilder builder = BeanMetaDataBuilder.createBuilder(name, DirectExecutor.class.getName());
builder.setMode(ControllerMode.ON_DEMAND);
builder.setFactoryClass(JBossExecutors.class.getName());
builder.setFactoryMethod("notatingExecutor");
builder.addConstructorParameter(Executor.class.getName(), builder.createInject(metaData.getParent()));
builder.addConstructorParameter(String.class.getName(), metaData.getNote());
beanMetaDataList.add(builder.getBeanMetaData());
}
for (ScheduledThreadPoolExecutorMetaData metaData : scheduledThreadPoolExecutors) {
final String name = metaData.getName();
final BeanMetaDataBuilder builder = BeanMetaDataBuilder.createBuilder(name, ScheduledThreadPoolExecutor.class.getName());
builder.setMode(ControllerMode.ON_DEMAND);
final PoolSizeMetaData poolSizeMetaData = metaData.getPoolSize();
final int size;
if (poolSizeMetaData != null) {
size = max(1, calcPoolSize(poolSizeMetaData));
} else {
size = 1;
}
builder.addConstructorParameter("int", Integer.valueOf(size));
final String threadFactoryName = metaData.getThreadFactory();
if (threadFactoryName != null) {
builder.addConstructorParameter(ThreadFactory.class.getName(), builder.createInject(threadFactoryName));
}
beanMetaDataList.add(builder.getBeanMetaData());
}
return beanMetaDataList;
}
private static int calcPoolSize(PoolSizeMetaData poolSizeMetaData) {
float count = poolSizeMetaData.getCount();
float perCpu = poolSizeMetaData.getPerCpu();
if (Float.isNaN(count) || Float.isInfinite(count) || count < 0.0f) {
count = 0.0f;
}
if (Float.isNaN(perCpu) || Float.isInfinite(perCpu) || perCpu < 0.0f) {
perCpu = 0.0f;
}
return Math.round(count + ((float) Runtime.getRuntime().availableProcessors()) * perCpu);
}
}