/*
* #%L
* Wisdom-Framework
* %%
* Copyright (C) 2013 - 2015 Wisdom Framework
* %%
* Licensed 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.
* #L%
*/
package org.wisdom.framework.instances;
import org.apache.felix.ipojo.*;
import org.apache.felix.ipojo.annotations.*;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ConfigurationEvent;
import org.osgi.service.cm.ConfigurationListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wisdom.framework.instances.api.InstantiatedBy;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Manages factories annotated with {@link InstantiatedByManager}.
* It tracks these factories and listens configuration admin instance, and binds them.
*/
@Component
@Provides
@Instantiate
public class InstantiatedByManager implements ConfigurationListener {
private static final Logger LOGGER = LoggerFactory.getLogger(InstantiatedByManager.class);
@Context
BundleContext context;
@Requires
ConfigurationAdmin admin;
private List<InstanceDeclaration> declarations = new ArrayList<>();
private final Lock lock = new ReentrantLock(true);
/**
* Bind a factory.
*
* @param factory the factory
*/
@Bind(aggregate = true)
public void bindFactory(Factory factory) {
// Only support primitive component
if (!(factory instanceof ComponentFactory)) {
return;
}
String cn = factory.getClassName();
if (cn == null) {
return;
}
// Has the factory the annotation
try {
Class clazz = ((ComponentFactory) factory).loadClass(cn);
InstantiatedBy annotation = (InstantiatedBy) clazz.getAnnotation(InstantiatedBy.class);
if (annotation != null) {
// Match !
LOGGER.info("Factory annotated with `@InstantiatedBy` found : {}, configuration : {}",
factory.getName(),
annotation.value());
addInstanceDeclaration(factory, annotation.value());
}
} catch (ClassNotFoundException e) {
LOGGER.error("Cannot load the component class {}", cn, e);
}
}
/**
* Unbinds a factory.
*
* @param factory the factory
*/
@Unbind
public void unbindFactory(Factory factory) {
try {
lock.lock();
InstanceDeclaration declaration = getDeclarationByFactory(factory);
if (declaration != null) {
LOGGER.info("Disposing instance created by");
declaration.dispose();
declarations.remove(declaration);
}
} finally {
lock.unlock();
}
}
private void addInstanceDeclaration(Factory factory, String value) {
try {
lock.lock();
// Do we know this factory already ?
InstanceDeclaration declaration = getDeclarationByFactory(factory);
if (declaration != null) {
declaration.dispose();
declarations.remove(declaration);
}
// Add it
declaration = new InstanceDeclaration(factory, value);
declarations.add(declaration);
// Do we have a configuration
Configuration[] configurations = getConfigurationList();
if (configurations != null) {
for (Configuration configuration : configurations) {
if (declaration.matches(configuration)) {
LOGGER.debug("Found a matching configuration for " + factory.getName() + " => " + configuration
.getPid());
declaration.attachOrUpdate(configuration);
}
}
}
} finally {
lock.unlock();
}
}
private Configuration[] getConfigurationList() {
Configuration[] configurations;
try {
configurations = admin.listConfigurations(null);
} catch (InvalidSyntaxException | IOException e) {
// Cannot happen as the filter is null.
throw new RuntimeException("Invalid Syntax Exception or IOException", e);
}
return configurations;
}
private InstanceDeclaration getDeclarationByFactory(Factory factory) {
for (InstanceDeclaration declaration : declarations) {
if (factory.equals(declaration.factory)) {
return declaration;
}
}
return null;
}
private List<InstanceDeclaration> getDeclarationsByConfiguration(String pid, String factoryPid) {
List<InstanceDeclaration> result = new ArrayList<>();
for (InstanceDeclaration declaration : declarations) {
if (declaration.matches(pid, factoryPid)) {
result.add(declaration);
}
}
return result;
}
/**
* Receives a configuration event.
*
* @param event the event.
*/
@Override
public void configurationEvent(ConfigurationEvent event) {
LOGGER.debug("event received : " + event.getPid() + " - " + event.getType());
try {
lock.lock();
final List<InstanceDeclaration> impacted
= getDeclarationsByConfiguration(event.getPid(), event.getFactoryPid());
if (impacted.isEmpty()) {
return;
}
switch (event.getType()) {
case ConfigurationEvent.CM_DELETED:
for (InstanceDeclaration declaration : impacted) {
LOGGER.info("Configuration " + event.getPid() + " deleted");
declaration.dispose(event.getPid());
}
break;
case ConfigurationEvent.CM_UPDATED:
for (InstanceDeclaration declaration : impacted) {
final Configuration configuration = find(event.getPid());
if (configuration == null) {
LOGGER.error("Weird case, a matching declaration was found, but cannot be found a second " +
"times, may be because of rapid changes in the config admin");
} else {
declaration.attachOrUpdate(configuration);
}
}
break;
}
} finally {
lock.unlock();
}
}
private Configuration find(String pid) {
final Configuration[] configurations = getConfigurationList();
if (configurations != null) {
for (Configuration conf : configurations) {
if (conf.getPid().equals(pid)) {
return conf;
}
}
}
return null;
}
/**
* Represent an instantiation request.
*/
private class InstanceDeclaration {
/**
* The factory.
*/
private final Factory factory;
/**
* The PID of the configuration, can be a configuration PID and factory PID.
*/
private final String target;
/**
* The component instance, created and disposed dynamically.
* The key if the configuration PID.
*/
private Map<String, ComponentInstance> instances = new LinkedHashMap<>();
/**
* Creates a new instance of {@link org.wisdom.framework.instances.InstantiatedByManager.InstanceDeclaration}.
*
* @param factory the factory
* @param target the target
*/
private InstanceDeclaration(Factory factory, String target) {
this.factory = factory;
this.target = target;
}
/**
* Disposes the all created instances.
*/
public void dispose() {
for (ComponentInstance instance : instances.values()) {
LOGGER.info("Disposing " + instance.getInstanceName());
instance.dispose();
}
instances.clear();
}
/**
* Disposes the instance matching the given configuration pid.
*
* @param pid the pid
*/
public void dispose(String pid) {
ComponentInstance instance = instances.remove(pid);
if (instance != null) {
LOGGER.info("Disposing " + instance.getInstanceName());
instance.dispose();
}
}
/**
* Checks whether or not the given configuration match the configuration name. It matches against the
* configuration PID and factory PID.
*
* @param configuration the configuration
* @return {@code true} if it matches, {@code false} otherwise
*/
public boolean matches(Configuration configuration) {
return matches(configuration.getPid(), configuration.getFactoryPid());
}
/**
* Checks whether of not the given pid **or** factory pid matches the current required configuration.
*
* @param pid the pid
* @param factoryPid the factory pid
* @return {@code true} if it matches, {@code false} otherwise
*/
public boolean matches(String pid, String factoryPid) {
return target.equals(pid)
|| target.equals(factoryPid);
}
private ComponentInstance create(Configuration configuration) {
try {
return factory.createComponentInstance(configuration.getProperties());
} catch (UnacceptableConfiguration | MissingHandlerException | ConfigurationException
e) {
LOGGER.error("Component creation failed from configuration {} ({})", configuration.getPid(),
configuration.getProperties(), e);
}
return null;
}
/**
* Attaches this request to the given configuration. It creates the iPOJO instance if it does not exist. It
* updates it if the instance was already created.
*
* @param configuration the configuration.
*/
public void attachOrUpdate(Configuration configuration) {
// Do we have a configuration with the same pid
final String pid = configuration.getPid();
ComponentInstance instance = instances.get(pid);
if (instance == null) {
LOGGER.info("Attaching {} to factory {}", pid, factory.getName());
instance = create(configuration);
if (instance != null) {
LOGGER.info("Instance {} created from {}", instance.getInstanceName(), pid);
instances.put(pid, instance);
}
} else {
instance.reconfigure(configuration.getProperties());
LOGGER.info("Instance {} reconfigured from {}", instance.getInstanceName(), pid);
}
}
}
}