/**
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.aries.blueprint.container;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
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.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.apache.aries.blueprint.reflect.MetadataUtil;
import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
import org.apache.aries.blueprint.container.BeanRecipe.UnwrapperedBeanHolder;
import org.apache.aries.blueprint.di.CircularDependencyException;
import org.apache.aries.blueprint.di.ExecutionContext;
import org.apache.aries.blueprint.di.IdRefRecipe;
import org.apache.aries.blueprint.di.Recipe;
import org.apache.aries.blueprint.di.RefRecipe;
import org.apache.aries.blueprint.di.Repository;
import org.apache.aries.blueprint.di.CollectionRecipe;
import org.osgi.service.blueprint.container.ReifiedType;
import org.osgi.service.blueprint.container.ComponentDefinitionException;
import org.osgi.service.blueprint.container.NoSuchComponentException;
import org.osgi.service.blueprint.reflect.BeanMetadata;
import org.osgi.service.blueprint.reflect.ComponentMetadata;
/**
* The default repository implementation
*/
public class BlueprintRepository implements Repository, ExecutionContext {
/**
* The blueprint container
*/
private final ExtendedBlueprintContainer blueprintContainer;
/**
* Contains object recipes
*/
private final Map<String, Recipe> recipes = new ConcurrentHashMap<String, Recipe>();
/**
* Contains object instances. Objects are stored as futures by the first task that wants to create it.
* All other listeners should call get on the future.
*/
private final ConcurrentMap<String, Future<Object>> instances = new ConcurrentHashMap<String, Future<Object>>();
/**
* Keep track of creation order
*/
private final List<String> creationOrder = new CopyOnWriteArrayList<String>();
/**
* Contains partial objects.
*/
private final ThreadLocal<Map<String, Object>> partialObjects = new ThreadLocal<Map<String,Object>>();
/**
* Before each recipe is executed it is pushed on the stack. The
* stack is used to detect circular dependencies.
*/
private final ThreadLocal<LinkedList<Recipe>> stack = new ThreadLocal<LinkedList<Recipe>>();
public BlueprintRepository(ExtendedBlueprintContainer container) {
blueprintContainer = container;
}
public Object getInstance(String name) {
Future<Object> future = instances.get(name);
if (future != null && future.isDone()) {
try {
return future.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
} catch (ExecutionException e) {
return null;
}
} else {
return null;
}
}
public Recipe getRecipe(String name) {
return recipes.get(name);
}
public Set<String> getNames() {
Set<String> names = new HashSet<String>();
names.addAll(recipes.keySet());
names.addAll(instances.keySet());
return names;
}
public void putRecipe(String name, Recipe recipe) {
if (instances.containsKey(name)) {
throw new ComponentDefinitionException("Name " + name + " is already registered to instance " + getInstance(name));
}
recipes.put(name, recipe);
}
public void removeRecipe(String name) {
if (instances.containsKey(name))
throw new ComponentDefinitionException("Name " + name + " is already instanciated as " + getInstance(name) + " and cannot be removed.");
recipes.remove(name);
}
private Object convert(String name, Object instance) throws ComponentDefinitionException {
try {
// Make sure to go through the conversion step in case we have a Convertible object
return convert(instance, new ReifiedType(Object.class));
} catch (Exception e) {
throw new ComponentDefinitionException("Unable to convert instance " + name, e);
}
}
public Object create(String name) throws ComponentDefinitionException {
ExecutionContext oldContext = ExecutionContext.Holder.setContext(this);
try {
Object instance = createInstance(name);
return convert(name, instance);
} finally {
ExecutionContext.Holder.setContext(oldContext);
}
}
public Object create(String name, Collection<Class<?>> proxyInterfaces) throws ComponentDefinitionException {
ExecutionContext oldContext = ExecutionContext.Holder.setContext(this);
try {
Object instance = createInstance(name);
if(instance instanceof UnwrapperedBeanHolder)
instance = BeanRecipe.wrap((UnwrapperedBeanHolder) instance, proxyInterfaces);
return convert(name, instance);
} finally {
ExecutionContext.Holder.setContext(oldContext);
}
}
public Map<String, Object> createAll(Collection<String> names, Collection<Class<?>> proxyInterfaces) throws ComponentDefinitionException {
ExecutionContext oldContext = ExecutionContext.Holder.setContext(this);
try {
Map<String, Object> instances = createInstances(names);
for (String name : instances.keySet()) {
Object obj = instances.get(name);
if(obj instanceof UnwrapperedBeanHolder)
obj = BeanRecipe.wrap((UnwrapperedBeanHolder) obj, proxyInterfaces);
instances.put(name, convert(name, obj));
}
return instances;
} finally {
ExecutionContext.Holder.setContext(oldContext);
}
}
public void createAll(Collection<String> names) throws ComponentDefinitionException {
ExecutionContext oldContext = ExecutionContext.Holder.setContext(this);
try {
createInstances(names);
return;
} finally {
ExecutionContext.Holder.setContext(oldContext);
}
}
public <T> List<T> getAllRecipes(Class<T> clazz, String... names) {
List<T> recipes = new ArrayList<T>();
for (Recipe r : getAllRecipes(names)) {
if (clazz.isInstance(r)) {
recipes.add(clazz.cast(r));
}
}
return recipes;
}
public Set<Recipe> getAllRecipes(String... names) {
ExecutionContext oldContext = ExecutionContext.Holder.setContext(this);
try {
Set<Recipe> allRecipes = new HashSet<Recipe>();
Collection<String> topLevel = names != null && names.length > 0 ? Arrays.asList(names) : recipes.keySet();
for (String name : topLevel) {
internalGetAllRecipes(allRecipes, getRecipe(name));
}
return allRecipes;
} finally {
ExecutionContext.Holder.setContext(oldContext);
}
}
/*
* This method should not be called directly, only from one of the getAllRecipes() methods.
*/
private void internalGetAllRecipes(Set<Recipe> allRecipes, Recipe r) {
if (r != null) {
if (allRecipes.add(r)) {
for (Recipe c : r.getDependencies()) {
internalGetAllRecipes(allRecipes, c);
}
}
}
}
private Object createInstance(String name) {
Object instance = getInstance(name);
if (instance == null) {
Map <String, Object> instances = createInstances(Arrays.asList(name));
instance = instances.get(name);
if (instance == null) {
throw new NoSuchComponentException(name);
}
}
return instance;
}
private Map<String, Object> createInstances(Collection<String> names) {
// Instance creation is synchronized inside each create method (via the use of futures), so that
// a recipe will only created once where appropriate
DependencyGraph graph = new DependencyGraph(this);
HashMap<String, Object> objects = new LinkedHashMap<String, Object>();
for (Map.Entry<String, Recipe> entry : graph.getSortedRecipes(names).entrySet()) {
String name = entry.getKey();
ComponentMetadata component = blueprintContainer.getComponentDefinitionRegistry().getComponentDefinition(name);
boolean prototype = (component instanceof BeanMetadata)
&& MetadataUtil.isPrototypeScope((BeanMetadata) component);
if (!prototype || names.contains(name)) {
objects.put(
name,
entry.getValue().create());
}
}
return objects;
}
public void validate() {
for (Recipe recipe : getAllRecipes()) {
// Check that references are satisfied
String ref = null;
if (recipe instanceof RefRecipe) {
ref = ((RefRecipe) recipe).getIdRef();
} else if (recipe instanceof IdRefRecipe) {
ref = ((IdRefRecipe) recipe).getIdRef();
}
if (ref != null && getRecipe(ref) == null) {
throw new ComponentDefinitionException("Unresolved ref/idref to component: " + ref);
}
// Check service
if (recipe instanceof ServiceRecipe) {
Recipe r = ((ServiceRecipe) recipe).getServiceRecipe();
if (r instanceof RefRecipe) {
r = getRecipe(((RefRecipe) r).getIdRef());
}
if (r instanceof ServiceRecipe) {
throw new ComponentDefinitionException("The target for a <service> element must not be <service> element");
}
if (r instanceof ReferenceListRecipe) {
throw new ComponentDefinitionException("The target for a <service> element must not be <reference-list> element");
}
CollectionRecipe listeners = ((ServiceRecipe) recipe).getListenersRecipe();
for (Recipe lr : listeners.getDependencies()) {
// The listener recipe is a bean recipe with the listener being set in a property
for (Recipe l : lr.getDependencies()) {
if (l instanceof RefRecipe) {
l = getRecipe(((RefRecipe) l).getIdRef());
}
if (l instanceof ServiceRecipe) {
throw new ComponentDefinitionException("The target for a <registration-listener> element must not be <service> element");
}
if (l instanceof ReferenceListRecipe) {
throw new ComponentDefinitionException("The target for a <registration-listener> element must not be <reference-list> element");
}
}
}
}
// Check references
if (recipe instanceof AbstractServiceReferenceRecipe) {
CollectionRecipe listeners = ((AbstractServiceReferenceRecipe) recipe).getListenersRecipe();
for (Recipe lr : listeners.getDependencies()) {
// The listener recipe is a bean recipe with the listener being set in a property
for (Recipe l : lr.getDependencies()) {
if (l instanceof RefRecipe) {
l = getRecipe(((RefRecipe) l).getIdRef());
}
if (l instanceof ServiceRecipe) {
throw new ComponentDefinitionException("The target for a <reference-listener> element must not be <service> element");
}
if (l instanceof ReferenceListRecipe) {
throw new ComponentDefinitionException("The target for a <reference-listener> element must not be <reference-list> element");
}
}
}
}
}
}
public void destroy() {
// destroy objects in reverse creation order
List<String> order = new ArrayList<String>(creationOrder);
Collections.reverse(order);
for (String name : order) {
Recipe recipe = recipes.get(name);
if (recipe != null) {
recipe.destroy(getInstance(name));
}
}
instances.clear();
creationOrder.clear();
}
public void push(Recipe recipe) {
LinkedList<Recipe> list = stack.get();
if (list != null && list.contains(recipe)) {
ArrayList<Recipe> circularity = new ArrayList<Recipe>(list.subList(list.indexOf(recipe), list.size()));
// remove anonymous nodes from circularity list
for (Iterator<Recipe> iterator = circularity.iterator(); iterator.hasNext();) {
Recipe item = iterator.next();
if (item != recipe && item.getName() == null) {
iterator.remove();
}
}
// add ending node to list so a full circuit is shown
circularity.add(recipe);
throw new CircularDependencyException(circularity);
}
if (list == null) {
list = new LinkedList<Recipe>();
stack.set(list);
}
list.add(recipe);
}
public Recipe pop() {
LinkedList<Recipe> list = stack.get();
return list.removeLast();
}
public boolean containsObject(String name) {
return instances.containsKey(name) || getRecipe(name) != null;
}
public Object getObject(String name) {
Future<Object> future = instances.get(name);
Object result = null;
if (future != null && future.isDone()) {
try {
result = future.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
result = getRecipe(name);
} catch (ExecutionException e) {
result = getRecipe(name);
}
} else {
result = getRecipe(name);
}
return result;
}
public Future<Object> addFullObject(String name, Future<Object> object) {
return instances.putIfAbsent(name, object);
}
public void addPartialObject(String name, Object object) {
if (partialObjects.get() == null)
partialObjects.set(new HashMap<String, Object>());
partialObjects.get().put(name, object);
}
public Object getPartialObject(String name) {
return (partialObjects.get() != null) ? partialObjects.get().get(name) : null;
}
public void removePartialObject(String name) {
creationOrder.add(name);
if (partialObjects.get() != null)
partialObjects.get().remove(name);
}
public Object convert(Object value, ReifiedType type) throws Exception {
return blueprintContainer.getConverter().convert(value, type);
}
public boolean canConvert(Object value, ReifiedType type) {
return blueprintContainer.getConverter().canConvert(value, type);
}
public Class loadClass(String typeName) throws ClassNotFoundException {
return blueprintContainer.loadClass(typeName);
}
}