/*
* Copyright 2009 NCHOVY
*
* 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.
*/
package org.krakenapps.filter.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.felix.ipojo.ComponentInstance;
import org.apache.felix.ipojo.ConfigurationException;
import org.apache.felix.ipojo.Factory;
import org.apache.felix.ipojo.MissingHandlerException;
import org.apache.felix.ipojo.UnacceptableConfiguration;
import org.krakenapps.filter.ActiveFilter;
import org.krakenapps.filter.ComponentDescription;
import org.krakenapps.filter.ComponentDescriptionParser;
import org.krakenapps.filter.Filter;
import org.krakenapps.filter.FilterEventListener;
import org.krakenapps.filter.FilterHandler;
import org.krakenapps.filter.FilterManager;
import org.krakenapps.filter.MessageSpec;
import org.krakenapps.filter.exception.AlreadyBoundException;
import org.krakenapps.filter.exception.DuplicatedFilterNameException;
import org.krakenapps.filter.exception.FilterNotBoundException;
import org.krakenapps.filter.exception.FilterNotFoundException;
import org.krakenapps.filter.exception.FilterFactoryNotFoundException;
import org.krakenapps.filter.exception.MessageSpecMismatchException;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.prefs.Preferences;
import org.osgi.service.prefs.PreferencesService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class provides default implementation for the {@link FilterManager}
* interface.
*
* @author xeraph
* @since 1.0.0
*/
public class DefaultFilterManager implements FilterManager {
final Logger logger = LoggerFactory.getLogger(DefaultFilterManager.class.getName());
private static final String KRAKEN_FILTER_INTERFACE = "org.krakenapps.filter.Filter";
private BundleContext bc;
private Map<String, Filter> filterMap;
private Map<String, ActiveFilterRunner> runnerMap;
private Map<String, List<Filter>> forwardBindMap;
private Map<String, List<Filter>> reverseBindMap;
private Map<String, FilterHandler> handlerMap;
private Map<String, ComponentInstance> componentMap;
private List<FilterEventListener> listeners;
private List<String> latestFilterFactories;
private FilterConfig filterConfig;
private FilterFactoryTracker factoryTracker;
public DefaultFilterManager(BundleContext bc) {
filterMap = new HashMap<String, Filter>();
runnerMap = new HashMap<String, ActiveFilterRunner>();
forwardBindMap = new HashMap<String, List<Filter>>();
reverseBindMap = new HashMap<String, List<Filter>>();
handlerMap = new HashMap<String, FilterHandler>();
componentMap = new HashMap<String, ComponentInstance>();
listeners = new ArrayList<FilterEventListener>();
this.bc = bc;
filterConfig = new FilterConfig(getSystemPreferences());
factoryTracker = new FilterFactoryTracker(bc, this, filterConfig);
factoryTracker.open();
}
@Override
public void loadFilter(int filterTypeIndex, String filterId) throws FilterFactoryNotFoundException,
DuplicatedFilterNameException {
if (latestFilterFactories == null) {
throw new FilterFactoryNotFoundException(null);
}
String filterFactoryName = latestFilterFactories.get(filterTypeIndex - 1);
if (filterFactoryName == null) {
throw new FilterFactoryNotFoundException(filterFactoryName);
}
loadFilter(filterFactoryName, filterId);
}
@Override
public void loadFilter(String factoryName, String filterId) throws FilterFactoryNotFoundException,
DuplicatedFilterNameException {
ServiceReference factoryReference = findFilterFactoryReference(factoryName);
if (factoryReference == null)
throw new FilterFactoryNotFoundException(factoryName);
ComponentDescription description = getComponentDescription(factoryReference);
Factory factory = (Factory) bc.getService(factoryReference);
Properties configuration = newServiceConfiguration(filterId, description);
ComponentInstance instance = null;
try {
instance = factory.createComponentInstance(configuration);
} catch (UnacceptableConfiguration e) {
logger.warn("load filter error: ", e);
throw new DuplicatedFilterNameException(filterId);
} catch (MissingHandlerException e) {
logger.warn("load filter error: ", e);
return;
} catch (ConfigurationException e) {
// not reachable. filter handler do not throw this exception
// actually.
logger.warn("load filter error: ", e);
return;
}
bc.ungetService(factoryReference);
if (instance == null) {
logger.warn("[{} -> {}] component initialization failed.", factoryName, filterId);
return;
}
synchronized (this) {
componentMap.put(filterId, instance);
for (FilterEventListener listener : listeners) {
listener.onFilterLoaded(filterId);
}
}
instance.start();
}
@Override
public void unloadFilter(String filterId) throws FilterNotFoundException {
synchronized (this) {
ComponentInstance instance = componentMap.remove(filterId);
if (instance == null) {
logger.warn("filter not found: " + filterId);
throw new FilterNotFoundException(filterId);
}
for (FilterEventListener listener : listeners) {
listener.onFilterUnloading(filterId);
}
instance.stop();
instance.dispose();
}
}
@Override
public void runFilter(String filterId, long period) throws FilterNotFoundException, IllegalThreadStateException {
Filter filter = getFilterInstance(filterId);
if (filter instanceof ActiveFilter) {
ActiveFilter activeFilter = (ActiveFilter) filter;
if (activeFilter.isRunning())
throw new IllegalThreadStateException(filterId + " thread is already running.");
ActiveFilterRunner runner = new ActiveFilterRunner(activeFilter, period);
synchronized (this) {
runnerMap.put(filterId, runner);
}
runner.start();
} else {
throw new FilterNotFoundException(filterId);
}
}
@Override
public void stopFilter(String filterId) throws FilterNotFoundException {
ActiveFilterRunner runner = runnerMap.get(filterId);
if (runner == null)
throw new FilterNotFoundException(filterId);
runner.stop();
}
@Override
public void registerFilter(FilterHandler filterHandler, String filterId, Filter filter) {
logger.info("registering filter: " + filterId);
synchronized (this) {
filterMap.put(filterId, filter);
forwardBindMap.put(filterId, new ArrayList<Filter>());
reverseBindMap.put(filterId, new ArrayList<Filter>());
handlerMap.put(filterId, filterHandler);
}
}
@Override
public void unregisterFilter(String filterId) {
logger.info("unregistering filter: " + filterId);
/*
* example) pid1 -> pid 2 -> pid3 if you want to unload pid 2, you
* should remove 2 forward link and 2 reverse link. forward link in
* bindMap: 1->2, 2->3 reverse link in reverseBindMap: 2->1, 3->2
*/
synchronized (this) {
// remove filter
Filter filter = filterMap.remove(filterId);
// stop thread
if (filter instanceof ActiveFilter) {
ActiveFilter activeFilter = (ActiveFilter) filter;
if (activeFilter.isRunning()) {
ActiveFilterRunner runner = runnerMap.remove(filterId);
if (runner != null) {
logger.debug("[{}] thread stopping.", filterId);
runner.stop();
runner.waitToFinish();
logger.debug("[{}] thread stopped.", filterId);
}
}
}
// remove 2->3
filterMap.remove(filterId);
List<Filter> filters2to3 = forwardBindMap.remove(filterId);
// remove 3->2
if (filters2to3 != null) {
for (Filter filter3 : filters2to3) {
// reverse lookup and remove pid
String filter3Pid = (String) filter3.getProperty("instance.name");
List<Filter> filters3to2 = reverseBindMap.get(filter3Pid);
filters3to2.remove(filter);
}
}
// remove 2->1
List<Filter> filters2to1 = reverseBindMap.get(filterId);
// remove 1->2
if (filters2to1 != null) {
for (Filter filter1 : filters2to1) {
// forward lookup and remove pid
String filter1Pid = (String) filter1.getProperty("instance.name");
List<Filter> filters1to2 = forwardBindMap.get(filter1Pid);
filters1to2.remove(filter);
// notify state changed
FilterHandler handler1 = handlerMap.get(filter1Pid);
handler1.stateChanged(convertToArray(filters1to2));
}
}
// remove event handler
handlerMap.remove(filterId);
// remove from db
filterConfig.removeFilterInstance(filterId);
}
}
private Properties newServiceConfiguration(String filterId, ComponentDescription description) {
Properties configuration = new Properties();
configuration.put("instance.name", filterId);
configuration.put("filter.manager", this);
configuration.put("filter.config", filterConfig);
return configuration;
}
private ServiceReference findFilterFactoryReference(String factoryName) {
try {
ServiceReference[] refs = bc.getServiceReferences(Factory.class.getName(), "(component.class="
+ factoryName + ")");
if (refs == null || refs.length == 0)
return null;
return refs[0];
} catch (InvalidSyntaxException e) {
// ignore it.
}
return null;
}
@Override
public List<String> getFilterFactoryNames() {
try {
// get all iPOJO factories and filter classes that implements kraken
// filter interface.
List<String> filterTypes = new ArrayList<String>();
ServiceReference[] refs = bc.getServiceReferences(Factory.class.getName(), null);
for (ServiceReference ref : refs) {
ComponentDescription componentDescription = getComponentDescription(ref);
List<String> specifications = componentDescription.getSpecifications();
if (isFilterFactory(specifications)) {
filterTypes.add(componentDescription.getImplementationClass());
}
}
latestFilterFactories = filterTypes;
return filterTypes;
} catch (InvalidSyntaxException e) {
// not reachable. ignore.
}
return null;
}
private ComponentDescription getComponentDescription(ServiceReference ref) {
if (ref == null)
return null;
Object property = ref.getProperty("component.description");
if (property == null)
return null;
String instanceName = (String) ref.getProperty("instance.name");
String description = property.toString();
return ComponentDescriptionParser.parse(instanceName, description);
}
private boolean isFilterFactory(List<String> specifications) {
for (String specification : specifications) {
if (specification.equals(KRAKEN_FILTER_INTERFACE))
return true;
}
return false;
}
@Override
public List<ComponentDescription> getFilterInstanceDescriptions() {
List<ComponentDescription> descriptions = new ArrayList<ComponentDescription>();
try {
ServiceReference[] refs = bc.getServiceReferences(Filter.class.getName(), null);
if (refs != null) {
for (ServiceReference ref : refs) {
ComponentDescription description = new ComponentDescription();
description.setInstanceName((String) ref.getProperty("instance.name"));
description.setFactoryName((String) ref.getProperty("factory.name"));
descriptions.add(description);
}
}
} catch (InvalidSyntaxException e) {
// not reachable. ignore.
}
return descriptions;
}
@Override
public void bindFilter(String fromFilterId, String toFilterId) throws FilterNotFoundException,
AlreadyBoundException, MessageSpecMismatchException {
synchronized (this) {
// get filter instances.
Filter fromFilter = getFilterInstance(fromFilterId);
Filter toFilter = getFilterInstance(toFilterId);
// match message specification
MessageSpec sendMessageSpec = fromFilter.getOutputMessageSpec();
MessageSpec[] receiveMessageSpecs = toFilter.getInputMessageSpecs();
if (matchMessageSpec(sendMessageSpec, receiveMessageSpecs) == false)
throw new MessageSpecMismatchException(fromFilterId, toFilterId, sendMessageSpec, receiveMessageSpecs);
// check if same filter already exists.
List<Filter> bindedFilters = forwardBindMap.get(fromFilterId);
for (Filter bindedFilter : bindedFilters) {
String instanceName = (String) bindedFilter.getProperty("instance.name");
if (instanceName.equals(toFilterId)) {
throw new AlreadyBoundException(fromFilterId, toFilterId);
}
}
// double check if same filter already exists.
List<Filter> reverseBindedFilters = reverseBindMap.get(toFilterId);
for (Filter reverseBindedFilter : reverseBindedFilters) {
String instanceName = (String) reverseBindedFilter.getProperty("instance.name");
if (instanceName.equals(fromFilterId)) {
throw new AlreadyBoundException(fromFilterId, toFilterId);
}
}
// bind and reverse bind
bindedFilters.add(toFilter);
reverseBindedFilters.add(fromFilter);
// save bind status
filterConfig.bindFilter(fromFilterId, toFilterId);
// notify changed state.
FilterHandler handler = handlerMap.get(fromFilterId);
handler.stateChanged(convertToArray(bindedFilters));
// notify event handlers
for (FilterEventListener listener : listeners) {
listener.onFilterBound(fromFilterId, toFilterId);
}
}
}
private boolean matchMessageSpec(MessageSpec outputMessageSpec, MessageSpec[] inputMessageSpecs) {
for (MessageSpec inputSpec : inputMessageSpecs)
if (isSatisfySpec(outputMessageSpec, inputSpec))
return true;
return false;
}
private boolean isSatisfySpec(MessageSpec sample, MessageSpec spec) {
// Version range of send specification has only one version. (lower ==
// upper)
return sample.getName().equals(spec.getName()) && sample.getLatestVersion().isInRange(sample.getVersionRange());
}
@Override
public void unbindFilter(String fromFilterId, String toFilterId) throws FilterNotFoundException,
FilterNotBoundException {
synchronized (this) {
// get filter instances.
Filter fromFilter = getFilterInstance(fromFilterId); // for
// validation
Filter toFilter = getFilterInstance(toFilterId);
// notify event handlers
for (FilterEventListener listener : listeners) {
listener.onFilterUnbinding(fromFilterId, toFilterId);
}
// unbind it.
List<Filter> bindedFilters = forwardBindMap.get(fromFilterId);
boolean isRemoved = bindedFilters.remove(toFilter);
List<Filter> reverseBindedFilters = reverseBindMap.get(toFilterId);
reverseBindedFilters.remove(fromFilter);
// save bind status
filterConfig.unbindFilter(fromFilterId, toFilterId);
// notify changed state.
FilterHandler handler = handlerMap.get(fromFilterId);
handler.stateChanged(convertToArray(bindedFilters));
// raise exception if filter not found.
if (isRemoved == false)
throw new FilterNotBoundException(fromFilterId, toFilterId);
}
}
@Override
public Filter[] getInputFilters(String filterId) {
synchronized (this) {
List<Filter> bindedFilters = reverseBindMap.get(filterId);
if (bindedFilters == null)
return new Filter[0];
return convertToArray(bindedFilters);
}
}
@Override
public Filter[] getOutputFilters(String filterId) {
synchronized (this) {
List<Filter> bindedFilters = forwardBindMap.get(filterId);
if (bindedFilters == null)
return new Filter[0];
return convertToArray(bindedFilters);
}
}
private Filter[] convertToArray(List<Filter> filterList) {
Filter[] filters = new Filter[filterList.size()];
int i = 0;
for (Filter filter : filterList) {
filters[i++] = filter;
}
return filters;
}
/*
* Get property value of specified filter instance.
*/
@Override
public Object getProperty(String filterId, String key) throws FilterNotFoundException {
synchronized (this) {
Filter filter = getFilterInstance(filterId);
return filter.getProperty(key);
}
}
/*
* Get all property keys of specified filter instance.
*/
@Override
public Set<String> getPropertyKeys(String filterId) throws FilterNotFoundException {
Filter filter = getFilterInstance(filterId);
return filter.getPropertyKeys();
}
/*
* Set property value for specific filter instance.
*/
@Override
public void setProperty(String filterId, String key, String value) throws FilterNotFoundException {
synchronized (this) {
Filter filter = getFilterInstance(filterId);
if (filter != null) {
filterConfig.setFilterProperty(filterId, key, value);
filter.setProperty(key, value);
} else {
throw new FilterNotFoundException(filterId);
}
// notify event handlers
for (FilterEventListener listener : listeners) {
listener.onFilterSet(filterId, key, value);
}
}
}
/*
* Unset property for specific filter instance.
*/
@Override
public void unsetProperty(String filterId, String key) throws FilterNotFoundException {
synchronized (this) {
Filter filter = getFilterInstance(filterId);
if (filter != null) {
filterConfig.unsetFilterProperty(filterId, key);
filter.unsetProperty(key);
} else {
throw new FilterNotFoundException(filterId);
}
// notify event handlers
for (FilterEventListener listener : listeners) {
listener.onFilterUnset(filterId, key);
}
}
}
/*
* Return filter instance or throws exception.
*/
private Filter getFilterInstance(String filterId) throws FilterNotFoundException {
Filter filter = filterMap.get(filterId);
if (filter == null)
throw new FilterNotFoundException(filterId);
return filter;
}
/*
* Return filter instance using id.
*/
@Override
public Filter getFilter(String filterId) {
synchronized (this) {
return filterMap.get(filterId);
}
}
@Override
public Filter findFilter(MessageSpec inputSpec, MessageSpec outputSpec) {
synchronized (this) {
for (String filterId : filterMap.keySet()) {
Filter filter = filterMap.get(filterId);
if (isMatchedFilter(filter, inputSpec, outputSpec))
return filter;
}
}
return null;
}
@Override
public Collection<Filter> findFilters(MessageSpec inputSpec, MessageSpec outputSpec) {
Collection<Filter> filters = new ArrayList<Filter>();
synchronized (this) {
for (String filterId : filterMap.keySet()) {
Filter filter = filterMap.get(filterId);
if (isMatchedFilter(filter, inputSpec, outputSpec))
filters.add(filter);
}
}
return filters;
}
private boolean isMatchedFilter(Filter filter, MessageSpec inputSpec, MessageSpec outputSpec) {
if (filter == null)
return false;
boolean matched = false;
if (inputSpec != null)
for (MessageSpec actualSpec : filter.getInputMessageSpecs())
if (inputSpec.isSubsetOf(actualSpec))
matched = true;
if (inputSpec != null && matched == false)
return false;
if (outputSpec != null)
if (!outputSpec.isSupersetOf(filter.getOutputMessageSpec()))
return false;
return true;
}
@Override
public void subscribeFilterEvent(FilterEventListener listener) {
synchronized (this) {
listeners.add(listener);
}
}
@Override
public void unsubscribeFilterEvent(FilterEventListener listener) {
synchronized (this) {
listeners.remove(listener);
}
}
private Preferences getSystemPreferences() {
ServiceReference ref = bc.getServiceReference(PreferencesService.class.getName());
PreferencesService prefsService = (PreferencesService) bc.getService(ref);
return prefsService.getSystemPreferences();
}
}