/*
* Copyright 2012 The Solmix Project
*
* 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 may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.gnu.org/licenses/
* or see the FSF site: http://www.fsf.org.
*/
package org.solmix.runtime.extension;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.solmix.runtime.Container;
import org.solmix.runtime.ContainerEvent;
import org.solmix.runtime.ContainerFactory;
import org.solmix.runtime.ContainerListener;
import org.solmix.runtime.Extension;
import org.solmix.runtime.bean.ConfiguredBeanProvider;
import org.solmix.runtime.resource.ObjectTypeResolver;
import org.solmix.runtime.resource.PropertiesResolver;
import org.solmix.runtime.resource.ResourceManager;
import org.solmix.runtime.resource.ResourceManagerImpl;
import org.solmix.runtime.resource.ResourceResolver;
import org.solmix.runtime.resource.SinglePropertyResolver;
/**
*
* @author solmix.f@gmail.com
* @version $Id$ 2013-11-3
*/
public class ExtensionContainer implements Container
{
private static final Logger LOG = LoggerFactory.getLogger(ExtensionContainer.class);
private final List<ContainerListener> containerListeners = new ArrayList<ContainerListener>(4);
private final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
/**
* Container status cycle CREATING->INITIALIZING->CREATED->CLOSING->CLOSED
*/
public static enum ContainerStatus
{
CREATING , INITIALIZING , CREATED , CLOSING , CLOSED;
}
protected final Map<Class<?>, Object> extensions;
protected final Set<Class<?>> missingBeans;
private final ExtensionManagerImpl extensionManager;
protected String id;
private ContainerStatus status;
private final Map<String, Object> properties = new ConcurrentHashMap<String, Object>(16, 0.75f, 4);
public ExtensionContainer()
{
this(null, null, Thread.currentThread().getContextClassLoader());
}
public ExtensionContainer(Map<Class<?>, Object> beans,
Map<String, Object> properties)
{
this(beans, properties, Thread.currentThread().getContextClassLoader());
}
public ExtensionContainer(Map<Class<?>, Object> beans)
{
this(beans, null, Thread.currentThread().getContextClassLoader());
}
public ExtensionContainer(Map<Class<?>, Object> beans,
Map<String, Object> properties, ClassLoader extensionClassLoader)
{
if (beans == null) {
extensions = new ConcurrentHashMap<Class<?>, Object>(16, 0.75f, 4);
} else {
extensions = new ConcurrentHashMap<Class<?>, Object>(beans);
}
missingBeans = new CopyOnWriteArraySet<Class<?>>();
setStatus( ContainerStatus.CREATING);
ContainerFactory.possiblySetDefaultContainer(this);
if (null == properties) {
properties = new HashMap<String, Object>();
}
ResourceManager rm=new ResourceManagerImpl();
properties.put(CONTAINER_PROPERTY_NAME, DEFAULT_CONTAINER_ID);
properties.put(DEFAULT_CONTAINER_ID, this);
ResourceResolver propertiesResolver = new PropertiesResolver(properties);
rm.addResourceResolver(propertiesResolver);
ResourceResolver defaultContainer = new SinglePropertyResolver(DEFAULT_CONTAINER_ID, this);
rm.addResourceResolver(defaultContainer);
rm.addResourceResolver(new ObjectTypeResolver(this));
rm.addResourceResolver(new ResourceResolver() {
@Override
public <T> T resolve(String resourceName, Class<T> resourceType) {
if (extensionManager != null) {
T t = extensionManager.getExtension(resourceName,
resourceType);
if (t == null) {
t = getExtension(resourceType);
}
return t;
}
return null;
}
@Override
public InputStream getAsStream(String name) {
return null;
}
});
extensions.put(ResourceManager.class,rm);
extensionManager= new ExtensionManagerImpl(new String[0],
extensionClassLoader,
extensions,
rm,
this);
setStatus(ContainerStatus.INITIALIZING);
extensionManager.load(new String[]{ExtensionManager.EXTENSION_LOCATION});
extensionManager.activateAllByType(ResourceResolver.class);
extensions.put(ExtensionManager.class, extensionManager);
}
@Override
public void setId(String id) {
this.id = id;
}
/**
* @return the status
*/
public ContainerStatus getStatus() {
return status;
}
/**
* {@inheritDoc}
*
* @see org.solmix.runtime.Container#getExtension(java.lang.Class)
*/
@Override
public <T> T getExtension(Class<T> beanType) {
Object obj = extensions.get(beanType);
if (obj == null) {
if (missingBeans.contains(beanType)) {
// missing extensions,return null
return null;
}
ConfiguredBeanProvider provider = (ConfiguredBeanProvider) extensions.get(ConfiguredBeanProvider.class);
if (provider == null) {
provider = createBeanProvider();
}
if (provider != null) {
Collection<?> objs = provider.getBeansOfType(beanType);
if (objs == null || objs.isEmpty()) {
} else if (objs != null && objs.size() == 1) {
for (Object o : objs) {
extensions.put(beanType, o);
}
} else {
if (beanType.isInterface()
&& beanType.isAnnotationPresent(Extension.class)) {
if(LOG.isDebugEnabled()){
LOG.debug("found more than one instance for "+beanType.getName()+
",but this is a extension interface ,return the default one,see getExtensionLoader()!");
}
extensions.put(beanType,
getExtensionLoader(beanType).getDefault());
}else{
if(LOG.isWarnEnabled()){
LOG.warn("found "+objs.size()+" instance for "+beanType.getName());
}
for (Object o : objs) {
extensions.put(beanType, o);
}
}
}
obj = extensions.get(beanType);
}
}
if (obj != null) {
return beanType.cast(obj);
} else {
missingBeans.add(beanType);
}
return null;
}
/**
* @return
*/
protected synchronized ConfiguredBeanProvider createBeanProvider() {
ConfiguredBeanProvider provider = (ConfiguredBeanProvider) extensions.get(ConfiguredBeanProvider.class);
if (provider == null) {
provider=extensionManager;
this.setExtension(provider, ConfiguredBeanProvider.class);
}
return provider;
}
/**
* {@inheritDoc}
*
* @see org.solmix.runtime.Container#setExtension(java.lang.Object, java.lang.Class)
*/
@Override
public <T> void setExtension(T bean, Class<T> beanType) {
extensions.put(beanType, bean);
missingBeans.remove(beanType);
}
/**
* {@inheritDoc}
*
* @see org.solmix.runtime.Container#hasExtensionByName(java.lang.String)
*/
@Override
public boolean hasExtensionByName(String name) {
for (Class<?> c : extensions.keySet()) {
if (name.equals(c.getName())) {
return true;
}
}
ConfiguredBeanProvider provider = (ConfiguredBeanProvider) extensions.get(ConfiguredBeanProvider.class);
if (provider == null) {
provider = createBeanProvider();
}
if (provider != null) {
return provider.hasBeanOfName(name);
}
return false;
}
/**
* {@inheritDoc}
*
* @see org.solmix.runtime.Container#getId()
*/
@Override
public String getId() {
return id == null ? DEFAULT_CONTAINER_ID +"-"+ Integer.toString(Math.abs(this.hashCode())) : id;
}
/**
* {@inheritDoc}
*
* @see org.solmix.runtime.Container#open()
*/
@Override
public void open() {
synchronized (this) {
setStatus(ContainerStatus.CREATED);
while (status == ContainerStatus.CREATED) {
try {
wait();
} catch (InterruptedException e) {
// ignored.
}
}
}
}
public void initialize() {
setStatus(ContainerStatus.INITIALIZING);
doInitializeInternal();
setStatus(ContainerStatus.CREATED);
if(LOG.isDebugEnabled())
LOG.debug("Container Created success for ID:"+getId());
}
/**
*
*/
protected void doInitializeInternal() {
extensionManager.initialize();
// init features
}
/**
* @param status the status to set
*/
public void setStatus(ContainerStatus status) {
this.status = status;
int type=ContainerEvent.CREATED;
switch (status) {
case CLOSED:
type=ContainerEvent.POSTCLOSE;
break;
case CLOSING:
type=ContainerEvent.PRECLOSE;
break;
case CREATED:
type=ContainerEvent.CREATED;
break;
case CREATING:
return;
case INITIALIZING:
return;
}
ContainerEvent event=new ContainerEvent(type,this,this);
fireContainerEvent(event);
}
/**
*
*/
protected void destroyBeans() {
}
@Override
public Map<String, Object> getProperties() {
return properties;
}
@Override
public void setProperties(Map<String, Object> map) {
properties.clear();
properties.putAll(map);
}
@Override
public Object getProperty(String s) {
return properties.get(s);
}
@Override
public void setProperty(String s, Object o) {
if (o == null) {
properties.remove(s);
} else {
properties.put(s, o);
}
}
@Override
public void addListener(ContainerListener l) {
synchronized (containerListeners) {
containerListeners.add(l);
}
}
@Override
public void removeListener(ContainerListener l) {
synchronized (containerListeners) {
containerListeners.remove(l);
}
}
private boolean firstFireContainerListener=true;
protected void fireContainerEvent(ContainerEvent event) {
List<ContainerListener> toNotify = null;
// Copy array
synchronized (containerListeners) {
//if not set ,call default.
if (firstFireContainerListener&&containerListeners.size()==0) {
firstFireContainerListener=false;
if(LOG.isTraceEnabled())
LOG.trace("NO found containerListener,try to load default ContainerListener!");
ConfiguredBeanProvider provider = (ConfiguredBeanProvider) extensions.get(ConfiguredBeanProvider.class);
if (provider == null) {
provider = createBeanProvider();
}
if (provider != null) {
Collection<? extends ContainerListener> listeners = provider.getBeansOfType(ContainerListener.class);
if (listeners != null)
containerListeners.addAll(listeners);
}
}
toNotify = new ArrayList<ContainerListener>(containerListeners);
}
// Notify all in toNotify
for (Iterator<ContainerListener> i = toNotify.iterator(); i.hasNext();) {
ContainerListener l = i.next();
l.handleEvent(event);
}
}
/**
* {@inheritDoc}
*
* @see org.solmix.runtime.Container#close()
*/
@Override
public void close() {
close(true);
}
/**
* {@inheritDoc}
*
* @see org.solmix.runtime.Container#close(boolean)
*/
@Override
public void close(boolean wait) {
if(status==ContainerStatus.CLOSING){
return ;
}
synchronized(this){
setStatus(ContainerStatus.CLOSING);
}
destroyBeans();
synchronized(this){
setStatus(ContainerStatus.CLOSED);
notifyAll();
}
if(LOG.isDebugEnabled())
LOG.debug("Container Closed for ID:"+getId());
if(ContainerFactory.getDefaultContainer(false)==this){
ContainerFactory.setDefaultContainer(null);
}
ContainerFactory.clearDefaultContainerForAnyThread(this);
}
/**
* {@inheritDoc}
*
* @see org.solmix.runtime.Container#getExtensionLoader(java.lang.Class)
*/
@SuppressWarnings("unchecked")
@Override
public <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null)
throw new IllegalArgumentException("Extension Type is null!");
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension Type:["
+ type.getName() + "] is not a interface!");
}
if (!type.isAnnotationPresent(Extension.class)) {
throw new IllegalArgumentException("Extension Type:["
+ type.getName() + "] ,without @"
+ Extension.class.getSimpleName() + " Annotation!");
}
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new DefaultExtensionLoader<T>(type,extensionManager));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
/**
* @return the containerListeners
*/
@Override
public List<ContainerListener> getContainerListeners() {
return containerListeners;
}
/**
* @param containerListeners the containerListeners to set
*/
@Override
public void setContainerListeners(List<ContainerListener> containerListeners) {
if(containerListeners!=null&&containerListeners.size()>0)
synchronized (this.containerListeners) {
this.containerListeners.clear();
this.containerListeners.addAll(containerListeners);
}
}
}