/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file 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.as.pojo.service;
import org.jboss.as.pojo.BeanState;
import org.jboss.as.pojo.logging.PojoLogger;
import org.jboss.as.pojo.descriptor.BeanMetaDataConfig;
import org.jboss.msc.service.DuplicateServiceException;
import org.jboss.msc.service.Service;
import org.jboss.msc.service.ServiceBuilder;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.ServiceRegistry;
import org.jboss.msc.service.ServiceTarget;
import org.jboss.msc.service.StartContext;
import org.jboss.msc.service.StartException;
import org.jboss.msc.service.StopContext;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Available instances per type
*
* @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a>
*/
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter"})
final class InstancesService implements Service<Set<Object>> {
private final Class<?> type;
private final Set<Object> instances = new HashSet<Object>(); // we do own locking, per type (service is also per type)
private static Map<TypeBeanStateKey, Set<Object>> beans = new HashMap<TypeBeanStateKey, Set<Object>>();
private static Map<TypeBeanStateKey, List<Callback>> incallbacks = new HashMap<TypeBeanStateKey, List<Callback>>();
private static Map<TypeBeanStateKey, List<Callback>> uncallbacks = new HashMap<TypeBeanStateKey, List<Callback>>();
private InstancesService(Class<?> type) {
this.type = type;
}
/**
* Add bean instance.
*
* @param registry the service registry
* @param target the service target
* @param state the bean state
* @param bean the bean
* @throws StartException for any error
*/
static void addInstance(ServiceRegistry registry, ServiceTarget target, BeanState state, Object bean) throws StartException {
addInstance(registry, target, state, bean.getClass(), bean);
}
/**
* Remove bean instance.
*
* @param registry the service registry
* @param state the bean state
* @param bean the bean
*/
static void removeInstance(ServiceRegistry registry, BeanState state, Object bean) {
removeInstance(registry, state, bean.getClass(), bean);
}
/**
* Add incallback.
*
* @param callback the callback
*/
static void addIncallback(Callback callback) {
addCallback(incallbacks, callback);
}
/**
* Add uncallback.
*
* @param callback the callback
*/
static void addUncallback(Callback callback) {
addCallback(uncallbacks, callback);
}
/**
* Remove incallback.
*
* @param callback the callback
*/
static void removeIncallback(Callback callback) {
removeCallback(incallbacks, callback);
}
/**
* Remove uncallback.
*
* @param callback the callback
*/
static void removeUncallback(Callback callback) {
removeCallback(uncallbacks, callback);
}
private static void addCallback(Map<TypeBeanStateKey, List<Callback>> map, Callback callback) {
final Class<?> type = callback.getType();
synchronized (type) {
if (map == incallbacks) {
try {
callback.dispatch(); // check all previous
} catch (Throwable t) {
PojoLogger.ROOT_LOGGER.errorAtIncallback(callback, t);
}
}
TypeBeanStateKey key = new TypeBeanStateKey(type, callback.getState());
List<Callback> callbacks = map.get(key);
if (callbacks == null) {
callbacks = new ArrayList<Callback>();
map.put(key, callbacks);
}
callbacks.add(callback);
}
}
private static void removeCallback(Map<TypeBeanStateKey, List<Callback>> map, Callback callback) {
final Class<?> type = callback.getType();
synchronized (type) {
TypeBeanStateKey key = new TypeBeanStateKey(type, callback.getState());
List<Callback> callbacks = map.get(key);
if (callbacks != null) {
callbacks.remove(callback);
if (callbacks.isEmpty())
map.remove(key);
}
if (map == uncallbacks) {
try {
callback.dispatch(); // try all remaining
} catch (Throwable t) {
PojoLogger.ROOT_LOGGER.errorAtUncallback(callback, t);
}
}
}
}
private static void invokeCallbacks(Map<TypeBeanStateKey, List<Callback>> map, BeanState state, final Class<?> clazz, Object bean) {
synchronized (clazz) {
TypeBeanStateKey key = new TypeBeanStateKey(clazz, state);
List<Callback> callbacks = map.get(key);
if (callbacks != null) {
for (Callback c : callbacks) {
try {
c.dispatch(bean);
} catch (Throwable t) {
PojoLogger.ROOT_LOGGER.invokingCallback(c, t);
}
}
}
}
}
private static void addInstance(ServiceRegistry registry, ServiceTarget target, BeanState state, final Class<?> clazz, Object bean) throws StartException {
if (clazz == null)
return;
ServiceName name = BeanMetaDataConfig.toInstancesName(clazz, state);
ServiceBuilder<Set<Object>> builder = target.addService(name, new InstancesService(clazz));
InstancesService service = putIfAbsent(registry, name, builder);
synchronized (clazz) {
service.instances.add(bean);
TypeBeanStateKey key = new TypeBeanStateKey(clazz, state);
if (beans.containsKey(key) == false)
beans.put(key, service.instances);
invokeCallbacks(incallbacks, state, clazz, bean);
}
addInstance(registry, target, state, clazz.getSuperclass(), bean);
Class<?>[] ifaces = clazz.getInterfaces();
for (Class<?> iface : ifaces)
addInstance(registry, target, state, iface, bean);
}
private static InstancesService putIfAbsent(ServiceRegistry registry, ServiceName name, ServiceBuilder builder) throws StartException {
for (; ; ) {
try {
ServiceController sc = registry.getService(name);
if (sc == null) {
sc = builder.install();
}
return (InstancesService) sc.getService();
} catch (DuplicateServiceException ignored) {
} catch (Exception e) {
throw new StartException(e);
}
}
}
private static void removeInstance(ServiceRegistry registry, BeanState state, final Class<?> clazz, Object bean) {
if (clazz == null)
return;
ServiceController controller = registry.getService(BeanMetaDataConfig.toInstancesName(clazz, state));
if (controller != null) {
InstancesService service = (InstancesService) controller.getService();
synchronized (clazz) {
service.instances.remove(bean);
invokeCallbacks(uncallbacks, state, clazz, bean);
if (service.instances.isEmpty()) {
beans.remove(new TypeBeanStateKey(clazz, state));
controller.setMode(ServiceController.Mode.REMOVE);
}
}
}
removeInstance(registry, state, clazz.getSuperclass(), bean);
Class<?>[] ifaces = clazz.getInterfaces();
for (Class<?> iface : ifaces)
removeInstance(registry, state, iface, bean);
}
@Override
public void start(StartContext context) throws StartException {
}
@Override
public void stop(StopContext context) {
}
@Override
public Set<Object> getValue() throws IllegalStateException, IllegalArgumentException {
synchronized (type) {
return Collections.unmodifiableSet(instances);
}
}
static Set<Object> getBeans(Class<?> type, BeanState state) {
synchronized (type) {
TypeBeanStateKey key = new TypeBeanStateKey(type, state);
Set<Object> objects = beans.get(key);
return (objects != null) ? Collections.unmodifiableSet(objects) : Collections.emptySet();
}
}
}