/** * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2011-2015 ForgeRock AS. All Rights Reserved * * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at * http://forgerock.org/license/CDDLv1.0.html * See the License for the specific language governing * permission and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at http://forgerock.org/license/CDDLv1.0.html * If applicable, add the following below the CDDL Header, * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * */ package org.forgerock.openidm.metadata.impl; import java.io.InputStream; import java.net.URL; import java.util.Collection; import java.util.Enumeration; import java.util.Map; import java.util.concurrent.ConcurrentSkipListMap; import org.forgerock.openidm.metadata.MetaDataProvider; import org.forgerock.openidm.metadata.MetaDataProviderCallback; import org.forgerock.openidm.osgi.ServiceTrackerListener; import org.forgerock.openidm.osgi.ServiceTrackerNotifier; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; /** * Keep track of meta data providers, either declared in bundle meta-data, or registered as services. */ public class ProviderTracker implements ServiceTrackerListener { final static Logger logger = LoggerFactory.getLogger(ProviderTracker.class); static ServiceTracker providerTracker; ProviderListener providerListener; BundleContext context; ObjectMapper mapper = new ObjectMapper(); // Map from origin identifiers to MetaDataProvider // Long type key are bundle identifiers // String keys are service pids // This map MUST be thread safe to avoid the java.util.ConcurrentModificationException Map<String, MetaDataProvider> providers = new ConcurrentSkipListMap<String, MetaDataProvider>(); /** * Constructor * @param context a bundle context to access OSGi * @param listener the listener to notify when a provider change was detecte * @param notifyDuringInit whether to notify the listener during the ProviderTracker construction. * Setting it to false allows to query the providers acquired during init with getProviders, and to process additional * providers via the listener. */ public ProviderTracker(BundleContext context, ProviderListener listener, boolean notifyDuringInit) { this.context = context; providerListener = listener; initBundleProviders(context, notifyDuringInit); providerTracker = initServiceTracker(context); // TODO: add bundle listeners to track new installs and remove uninstalls } private void initBundleProviders(BundleContext context, boolean notifyDuringInit) { Bundle[] bundles = context.getBundles(); for (Bundle bundle : bundles) { if (logger.isTraceEnabled()) { logger.trace("Scanning bundle {} for metadata file", bundle.getBundleId()); } Enumeration<URL> entries = bundle.findEntries("org/forgerock/metadata", "bundle.json", true); try { if (entries != null && entries.hasMoreElements()) { URL entryUrl = entries.nextElement(); logger.trace("Found metadata file, load and parse {}", entryUrl); InputStream in = entryUrl.openStream(); Map metaConfig = mapper.readValue(in, Map.class); in.close(); String providerClazzName = (String) metaConfig.get("metaDataProvider"); logger.trace("Loading declared MetaDataProvider {}", providerClazzName); if (providerClazzName == null) { logger.trace("No MetaDataProvider class declared in meta data file {} for {}", entryUrl, bundle.getSymbolicName()); } else { logger.trace("Loading declared MetaDataProvider {}", providerClazzName); Class providerClazz = bundle.loadClass(providerClazzName); MetaDataProvider provider = (MetaDataProvider) providerClazz.newInstance(); String id = Long.valueOf(bundle.getBundleId()).toString(); // Instantiate and set the provider callback provider.setCallback(new ProviderTrackerCallback(provider, id)); // Add the provider to the listener addProvider(id, provider, notifyDuringInit); logger.debug("Registered MetaDataProvider {} for {}", providerClazzName, bundle.getSymbolicName()); } } } catch (Exception ex) { logger.warn("Failed to obtain meta-data on handling configuration for {}", bundle.getSymbolicName(), ex); } } } private void addProvider(String originId, MetaDataProvider provider, boolean notify) { providers.put(originId, provider); if (providerListener != null && notify) { logger.debug("Notifying listener of added MetaDataProvider {}", originId); providerListener.addedProvider(originId, provider); } } private ServiceTracker initServiceTracker(BundleContext context) { ServiceTracker tracker = new ServiceTrackerNotifier(context, MetaDataProvider.class.getName(), null, this); tracker.open(); return tracker; } public void addedService(ServiceReference reference, Object service) { String pid = (String) reference.getProperty(Constants.SERVICE_PID); MetaDataProvider provider = (MetaDataProvider) service; // Instantiate and set the provider callback provider.setCallback(new ProviderTrackerCallback(provider, pid)); // Add the provider to the listener addProvider(pid, provider, true); } public void removedService(ServiceReference reference, Object service) { String pid = (String) reference.getProperty(Constants.SERVICE_PID); providers.remove(pid); } public void modifiedService(ServiceReference reference, Object service) { String pid = (String) reference.getProperty(Constants.SERVICE_PID); modifiedProvider(pid, (MetaDataProvider) service, true); } public void modifiedProvider(String pid, MetaDataProvider provider, boolean notify) { addProvider(pid, provider, notify); } /** * Get the current registered providers * Providers can be backed by an OSGi services, * or created from a bundle factory * @return meta data providers */ public Collection<MetaDataProvider> getProviders() { //The returned value MUST be thread safe! return providers.values(); } /** * A MetaDataProviderCallback implementation that is used to notify the ProvicerListener * when a MetaDataProvider calls refresh() */ private class ProviderTrackerCallback implements MetaDataProviderCallback { private MetaDataProvider provider = null; private String originId = null; /** * Constructor * @param provider the MetaDataProvider instance * @param originId the MetaDataProvider id */ public ProviderTrackerCallback(MetaDataProvider provider, String originId) { this.provider = provider; this.originId = originId; } @Override public void refresh() { modifiedProvider(originId, provider, true); } } }