/** * Copyright (c) Codice Foundation * <p> * 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 3 of the * License, or any later version. * <p> * This program 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. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. */ package ddf.catalog.util.impl; import java.util.Collection; import java.util.Collections; import java.util.Dictionary; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; import java.util.Set; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceEvent; import org.osgi.framework.ServiceReference; import org.osgi.service.event.Event; import org.osgi.service.event.EventConstants; import org.osgi.service.event.EventHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ddf.catalog.util.Describable; public class DescribableServiceMap<V extends Describable> implements Map<String, V>, EventHandler { private static final Logger LOGGER = LoggerFactory.getLogger(DescribableServiceMap.class); private static final String READ_ONLY_ERROR_MESSAGE = "This map is meant to be read only."; private Map<String, V> serviceMap = Collections.synchronizedMap(new HashMap<>()); private Map<String, String> pidToNameMap = Collections.synchronizedMap(new HashMap<>()); public DescribableServiceMap() { String[] topics = new String[] {"org/osgi/framework/ServiceEvent/MODIFIED"}; Dictionary props = new Hashtable(); props.put(EventConstants.EVENT_TOPIC, topics); BundleContext context = getContext(); if (context != null) { context.registerService(EventHandler.class.getName(), this, props); } } protected BundleContext getContext() { Bundle bundle = FrameworkUtil.getBundle(DescribableServiceMap.class); if (bundle != null) { return bundle.getBundleContext(); } return null; } public void bind(ServiceReference ref) { LOGGER.debug("{} Binding {}", this, ref); BundleContext context = getContext(); if (ref != null && context != null) { try { V service = (V) context.getService(ref); serviceMap.put(service.getId(), service); pidToNameMap.put((String) ref.getProperty(Constants.SERVICE_PID), service.getId()); } catch (ClassCastException e) { LOGGER.debug("Service {} could not be added to service map {} due to incorrect type", ref, this, e); } } else { LOGGER.debug("BundleContext was null, unable to add service reference"); } } public void unbind(ServiceReference ref) { BundleContext context = getContext(); if (ref != null && context != null) { LOGGER.debug("{} Unbinding {}", this, ref); V service = (V) context.getService(ref); if (service != null) { pidToNameMap.remove(ref.getProperty(Constants.SERVICE_PID)); serviceMap.remove(service.getId()); } } } @Override public int size() { return serviceMap.size(); } @Override public boolean isEmpty() { return serviceMap.isEmpty(); } @Override public boolean containsKey(Object key) { return serviceMap.containsKey(key); } @Override public boolean containsValue(Object value) { return serviceMap.containsValue(value); } @Override public V get(Object key) { return serviceMap.get(key); } @Override public V put(String key, V value) { throw new UnsupportedOperationException(READ_ONLY_ERROR_MESSAGE); } @Override public V remove(Object key) { throw new UnsupportedOperationException(READ_ONLY_ERROR_MESSAGE); } @Override public void putAll(Map<? extends String, ? extends V> m) { throw new UnsupportedOperationException(READ_ONLY_ERROR_MESSAGE); } @Override public void clear() { throw new UnsupportedOperationException(READ_ONLY_ERROR_MESSAGE); } @Override public Set<String> keySet() { return serviceMap.keySet(); } @Override public Collection<V> values() { return serviceMap.values(); } @Override public Set<Entry<String, V>> entrySet() { return serviceMap.entrySet(); } /** * The handleEvent method takes care of keeping the internal describable map up to date when * service name changes occur. * * @param event */ @Override public void handleEvent(Event event) { if (((ServiceEvent) event.getProperty(EventConstants.EVENT)).getType() != ServiceEvent.MODIFIED) { return; } String pid = event.getProperty(Constants.SERVICE_PID) .toString(); String oldName = pidToNameMap.get(pid); //if we don't have it in our map then it's a service we don't care about if (oldName == null) { return; } V service = serviceMap.get(oldName); if (service == null) { return; } String newName = service.getId(); if (!oldName.equals(newName)) { pidToNameMap.put(pid, newName); serviceMap.remove(oldName); serviceMap.put(newName, service); LOGGER.debug("Changed mapping name from {} to {}", oldName, newName); } } }