| /******************************************************************************* |
| * Copyright (c) 2008, 2010 VMware Inc. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * VMware Inc. - initial contribution |
| *******************************************************************************/ |
| |
| package org.eclipse.virgo.nano.core.internal.blueprint; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ScheduledExecutorService; |
| |
| import org.osgi.framework.Bundle; |
| 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 org.eclipse.virgo.kernel.diagnostics.KernelLogEvents; |
| import org.eclipse.virgo.medic.eventlog.EventLogger; |
| |
| /** |
| * {@link ApplicationContextDependencyMonitor} is a class that tracks the satisfaction of service dependencies needed |
| * during the creation of application contexts and issues log messages for delayed service dependencies. |
| * <p /> |
| * |
| * <strong>Concurrent Semantics</strong><br /> |
| * |
| * This class is thread safe. |
| * |
| */ |
| public final class ApplicationContextDependencyMonitor implements EventHandler { |
| |
| private static final String TOPIC_BLUEPRINT_EVENTS = "org/osgi/service/blueprint/container/"; |
| |
| private static final String EVENT_WAITING = TOPIC_BLUEPRINT_EVENTS + "WAITING"; |
| |
| private static final String EVENT_GRACE_PERIOD = TOPIC_BLUEPRINT_EVENTS + "GRACE_PERIOD"; |
| |
| private static final String EVENT_FAILURE = TOPIC_BLUEPRINT_EVENTS + "FAILURE"; |
| |
| private static final String EVENT_CREATED = TOPIC_BLUEPRINT_EVENTS + "CREATED"; |
| |
| private static final int MAXIMUM_WARNING_INTERVAL = 60 * 1000; |
| |
| private static final int WARNING_INTERVAL_INCREASE_RATE_PERCENT = 200; |
| |
| private static final int INITIAL_WARNING_INTERVAL = 5 * 1000; |
| |
| private static final int SLOW_WARNING_INTERVAL = 5 * 60 * 1000; |
| |
| private final Logger logger = LoggerFactory.getLogger(this.getClass()); |
| |
| private final EventLogger eventLogger; |
| |
| private final ScheduledExecutorService scheduledExecutorService; |
| |
| private final Map<Bundle, Map<ServiceDependency, Ticker>> tickers = new HashMap<Bundle, Map<ServiceDependency, Ticker>>(); |
| |
| private final Object monitor = new Object(); |
| |
| /** |
| * Construct a {@link ApplicationContextDependencyMonitor} which uses the given {@link ScheduledExecutorService} to |
| * schedule its warning messages. |
| * |
| * @param scheduledExecutorService the {@link ScheduledExecutorService} for scheduling warning messages |
| * @param eventLogger |
| */ |
| public ApplicationContextDependencyMonitor(ScheduledExecutorService scheduledExecutorService, EventLogger eventLogger) { |
| this.scheduledExecutorService = scheduledExecutorService; |
| this.eventLogger = eventLogger; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void handleEvent(Event event) { |
| synchronized (this.monitor) { |
| Bundle bundle = (Bundle) event.getProperty(EventConstants.BUNDLE); |
| |
| if (EVENT_WAITING.equals(event.getTopic())) { |
| List<ServiceDependency> serviceDependencies = createServiceDependencies(event); |
| if (serviceDependencies != null) { |
| for (ServiceDependency serviceDependency : serviceDependencies) { |
| addServiceDependencyTicker(serviceDependency, bundle); |
| } |
| } |
| } else if (EVENT_GRACE_PERIOD.equals(event.getTopic())) { |
| List<ServiceDependency> remainingUnsatisfiedDependencies = createServiceDependencies(event); |
| if (remainingUnsatisfiedDependencies != null) { |
| changeInUnsatisfiedDependencies(remainingUnsatisfiedDependencies, bundle); |
| } |
| |
| } else if (EVENT_FAILURE.equals(event.getTopic())) { |
| String[] dependenciesArray = (String[]) event.getProperty("dependencies"); |
| if (dependenciesArray != null) { |
| List<ServiceDependency> serviceDependencies = createServiceDependencies(event); |
| if (serviceDependencies != null) { |
| serviceDependenciesTimedOut(serviceDependencies, bundle); |
| } |
| } else { |
| containerCreationFailed(bundle); |
| } |
| } else if (EVENT_CREATED.equals(event.getTopic())) { |
| containerCreated(bundle); |
| } |
| } |
| } |
| |
| private void serviceDependenciesTimedOut(List<ServiceDependency> timedOutDependencies, Bundle bundle) { |
| Map<ServiceDependency, Ticker> bundlesTickers = this.tickers.get(bundle); |
| |
| if (bundlesTickers != null) { |
| for (ServiceDependency timedOutDependency : timedOutDependencies) { |
| Ticker ticker = bundlesTickers.remove(timedOutDependency); |
| if (ticker != null) { |
| dependencyTimedOut(timedOutDependency, ticker, bundle); |
| } |
| } |
| } |
| } |
| |
| private void containerCreationFailed(Bundle bundle) { |
| Map<ServiceDependency, Ticker> tickers = this.tickers.remove(bundle); |
| if (tickers != null) { |
| for (Entry<ServiceDependency, Ticker> ticker : tickers.entrySet()) { |
| ticker.getValue().cancel(); |
| } |
| } |
| } |
| |
| private void containerCreated(Bundle bundle) { |
| Map<ServiceDependency, Ticker> bundlesTickers = this.tickers.remove(bundle); |
| |
| if (bundlesTickers != null) { |
| for (Entry<ServiceDependency, Ticker> entry : bundlesTickers.entrySet()) { |
| dependencySatisfied(entry.getKey(), entry.getValue(), bundle); |
| } |
| } |
| } |
| |
| private void changeInUnsatisfiedDependencies(List<ServiceDependency> remainingUnsatisfiedDependencies, Bundle bundle) { |
| Map<ServiceDependency, Ticker> tickers = this.tickers.get(bundle); |
| |
| if (tickers != null) { |
| Iterator<Entry<ServiceDependency, Ticker>> entries = tickers.entrySet().iterator(); |
| |
| while (entries.hasNext()) { |
| Entry<ServiceDependency, Ticker> entry = entries.next(); |
| |
| if (!remainingUnsatisfiedDependencies.contains(entry.getKey())) { |
| dependencySatisfied(entry.getKey(), entry.getValue(), bundle); |
| entries.remove(); |
| } |
| } |
| } |
| } |
| |
| private void dependencySatisfied(ServiceDependency serviceDependency, Ticker ticker, Bundle bundle) { |
| logger.info("Service dependency '{}' has been satisfied", serviceDependency); |
| handleRemovedTicker(ticker, serviceDependency, bundle, true); |
| } |
| |
| private void dependencyTimedOut(ServiceDependency serviceDependency, Ticker ticker, Bundle bundle) { |
| logger.info("Service dependency '{}' has timed out", serviceDependency); |
| handleRemovedTicker(ticker, serviceDependency, bundle, false); |
| } |
| |
| private void handleRemovedTicker(Ticker ticker, ServiceDependency serviceDependency, Bundle bundle, boolean satisfied) { |
| boolean hasTicked = ticker.cancel(); |
| if (hasTicked) { |
| if (satisfied) { |
| this.eventLogger.log(KernelLogEvents.APPLICATION_CONTEXT_DEPENDENCY_SATISFIED, serviceDependency.getBeanName(), |
| bundle.getSymbolicName(), bundle.getVersion(), serviceDependency.getFilter()); |
| } else { |
| this.eventLogger.log(KernelLogEvents.APPLICATION_CONTEXT_DEPENDENCY_TIMED_OUT, serviceDependency.getBeanName(), |
| bundle.getSymbolicName(), bundle.getVersion(), serviceDependency.getFilter()); |
| } |
| } |
| } |
| |
| /** |
| * Add a service dependency ticker for the given application context, given associated bundle, and given service |
| * dependency. |
| * |
| * @param applicationContext the partially constructed application context which needs the service dependency |
| * @param serviceDependency the service dependency |
| * @param bundle the {@link Bundle} associated with the given application context |
| */ |
| private void addServiceDependencyTicker(final ServiceDependency serviceDependency, final Bundle bundle) { |
| Map<ServiceDependency, Ticker> serviceDependencyTickers = getServiceDependencyTickers(bundle); |
| if (serviceDependencyTickers.containsKey(serviceDependency)) { |
| logger.warn("Service dependency '{}' already being waited upon", serviceDependency); |
| } else { |
| // Services which are flagged as likely to be slow to be published are given a longer initial warning |
| // interval. |
| boolean slowService = serviceDependency.getFilter().contains("(org.eclipse.virgo.server.slowservice=true)"); |
| serviceDependencyTickers.put(serviceDependency, StandardTicker.createExponentialTicker(slowService ? SLOW_WARNING_INTERVAL |
| : INITIAL_WARNING_INTERVAL, WARNING_INTERVAL_INCREASE_RATE_PERCENT, slowService ? SLOW_WARNING_INTERVAL : MAXIMUM_WARNING_INTERVAL, |
| new Callable<Void>() { |
| |
| public Void call() throws Exception { |
| synchronized (ApplicationContextDependencyMonitor.this.monitor) { |
| if (bundle.getState() == Bundle.UNINSTALLED) { |
| ApplicationContextDependencyMonitor.this.containerCreationFailed(bundle); |
| } else { |
| eventLogger.log(KernelLogEvents.APPLICATION_CONTEXT_DEPENDENCY_DELAYED, serviceDependency.getBeanName(), |
| bundle.getSymbolicName(), bundle.getVersion(), serviceDependency.getFilter()); |
| } |
| return null; |
| } |
| } |
| }, this.scheduledExecutorService)); |
| } |
| } |
| |
| /** |
| * Get the possibly empty map of service dependency tickers for the given <code>Bundle</code>. |
| * |
| * @param bundle the <code>Bundle</code> whose application context's service dependencies are required |
| * @return a map of service dependency tickers |
| */ |
| private Map<ServiceDependency, Ticker> getServiceDependencyTickers(Bundle bundle) { |
| Map<ServiceDependency, Ticker> tickers = this.tickers.get(bundle); |
| if (tickers == null) { |
| tickers = new HashMap<ServiceDependency, Ticker>(); |
| this.tickers.put(bundle, tickers); |
| } |
| return tickers; |
| } |
| |
| public void stop() { |
| this.scheduledExecutorService.shutdown(); |
| } |
| |
| private List<ServiceDependency> createServiceDependencies(Event event) { |
| String[] filters = (String[]) event.getProperty("dependencies"); |
| String[] beanNames = (String[]) event.getProperty("bean.name"); |
| |
| List<ServiceDependency> serviceDependencies = new ArrayList<ServiceDependency>(); |
| if (filters != null && beanNames != null) { |
| for (int i = 0; i < filters.length; i++) { |
| serviceDependencies.add(new ServiceDependency(filters[i], beanNames[i])); |
| } |
| return serviceDependencies; |
| } |
| |
| /* |
| * Return null when filters is non-null and beanNames is null. Blueprint events sometimes lack this information, |
| * but a corresponding event including bean names is posted by |
| * BlueprintEventPostingOsgiBundleApplicationContextListener on receipt of the underlying Spring DM event. A |
| * return value of null indicates that the caller should ignore this event. |
| */ |
| return filters == null ? serviceDependencies : null; |
| } |
| |
| private static final class ServiceDependency { |
| |
| private final String filter; |
| |
| private final String beanName; |
| |
| private ServiceDependency(String filter, String beanName) { |
| this.filter = filter; |
| this.beanName = beanName; |
| } |
| |
| public String getFilter() { |
| return filter; |
| } |
| |
| public String getBeanName() { |
| return beanName; |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + beanName.hashCode(); |
| result = prime * result + filter.hashCode(); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) |
| return true; |
| if (obj == null) |
| return false; |
| if (getClass() != obj.getClass()) |
| return false; |
| |
| ServiceDependency other = (ServiceDependency) obj; |
| |
| if (!beanName.equals(other.beanName)) |
| return false; |
| |
| if (!filter.equals(other.filter)) |
| return false; |
| |
| return true; |
| } |
| |
| public String toString() { |
| return this.filter + " " + this.beanName; |
| } |
| } |
| } |