/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.brooklyn.core.mgmt.ha; import java.io.File; import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicReference; import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.BrooklynVersion; import org.apache.brooklyn.core.server.BrooklynServerConfig; import org.apache.brooklyn.core.server.BrooklynServerPaths; import org.apache.brooklyn.rt.felix.EmbeddedFelixFramework; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.core.osgi.Osgis; import org.apache.brooklyn.util.core.osgi.Osgis.BundleFinder; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.os.Os; import org.apache.brooklyn.util.os.Os.DeletionResult; import org.apache.brooklyn.util.repeat.Repeater; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Duration; import org.osgi.framework.Bundle; import org.osgi.framework.launch.Framework; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; public class OsgiManager { private static final Logger log = LoggerFactory.getLogger(OsgiManager.class); public static final ConfigKey<Boolean> USE_OSGI = BrooklynServerConfig.USE_OSGI; /* see Osgis for info on starting framework etc */ protected ManagementContext mgmt; protected Framework framework; protected File osgiCacheDir; public OsgiManager(ManagementContext mgmt) { this.mgmt = mgmt; } public void start() { try { osgiCacheDir = BrooklynServerPaths.getOsgiCacheDirCleanedIfNeeded(mgmt); // any extra OSGi startup args could go here framework = Osgis.getFramework(osgiCacheDir.getAbsolutePath(), false); } catch (Exception e) { throw Exceptions.propagate(e); } } public void stop() { Osgis.ungetFramework(framework); if (BrooklynServerPaths.isOsgiCacheForCleaning(mgmt, osgiCacheDir)) { // See exception reported in https://issues.apache.org/jira/browse/BROOKLYN-72 // We almost always fail to delete he OSGi temp directory due to a concurrent modification. // Therefore keep trying. final AtomicReference<DeletionResult> deletionResult = new AtomicReference<DeletionResult>(); Repeater.create("Delete OSGi cache dir") .until(new Callable<Boolean>() { @Override public Boolean call() { deletionResult.set(Os.deleteRecursively(osgiCacheDir)); return deletionResult.get().wasSuccessful(); }}) .limitTimeTo(Duration.ONE_SECOND) .backoffTo(Duration.millis(50)) .run(); if (deletionResult.get().getThrowable()!=null) { log.debug("Unable to delete "+osgiCacheDir+" (possibly being modified concurrently?): "+deletionResult.get().getThrowable()); } } osgiCacheDir = null; framework = null; } public synchronized void registerBundle(CatalogBundle bundle) { try { if (checkBundleInstalledThrowIfInconsistent(bundle)) { return; } Bundle b = Osgis.install(framework, bundle.getUrl()); checkCorrectlyInstalled(bundle, b); } catch (Exception e) { Exceptions.propagateIfFatal(e); throw new IllegalStateException("Bundle from "+bundle.getUrl()+" failed to install: " + e.getMessage(), e); } } private void checkCorrectlyInstalled(CatalogBundle bundle, Bundle b) { String nv = b.getSymbolicName()+":"+b.getVersion().toString(); if (!isBundleNameEqualOrAbsent(bundle, b)) { throw new IllegalStateException("Bundle already installed as "+nv+" but user explicitly requested "+bundle); } List<Bundle> matches = Osgis.bundleFinder(framework) .symbolicName(b.getSymbolicName()) .version(b.getVersion().toString()) .findAll(); if (matches.isEmpty()) { log.error("OSGi could not find bundle "+nv+" in search after installing it from "+bundle.getUrl()); } else if (matches.size()==1) { log.debug("Bundle from "+bundle.getUrl()+" successfully installed as " + nv + " ("+b+")"); } else { log.warn("OSGi has multiple bundles matching "+nv+", when just installed from "+bundle.getUrl()+": "+matches+"; " + "brooklyn will prefer the URL-based bundle for top-level references but any dependencies or " + "import-packages will be at the mercy of OSGi. " + "It is recommended to use distinct versions for different bundles, and the same URL for the same bundles."); } } private boolean checkBundleInstalledThrowIfInconsistent(CatalogBundle bundle) { String bundleUrl = bundle.getUrl(); if (bundleUrl != null) { Maybe<Bundle> installedBundle = Osgis.bundleFinder(framework).requiringFromUrl(bundleUrl).find(); if (installedBundle.isPresent()) { Bundle b = installedBundle.get(); String nv = b.getSymbolicName()+":"+b.getVersion().toString(); if (!isBundleNameEqualOrAbsent(bundle, b)) { throw new IllegalStateException("User requested bundle " + bundle + " but already installed as "+nv); } else { log.trace("Bundle from "+bundleUrl+" already installed as "+nv+"; not re-registering"); } return true; } } else { Maybe<Bundle> installedBundle = Osgis.bundleFinder(framework).symbolicName(bundle.getSymbolicName()).version(bundle.getVersion()).find(); if (installedBundle.isPresent()) { log.trace("Bundle "+bundle+" installed from "+installedBundle.get().getLocation()); } else { throw new IllegalStateException("Bundle "+bundle+" not previously registered, but URL is empty."); } return true; } return false; } public static boolean isBundleNameEqualOrAbsent(CatalogBundle bundle, Bundle b) { return !bundle.isNameResolved() || (bundle.getSymbolicName().equals(b.getSymbolicName()) && bundle.getVersion().equals(b.getVersion().toString())); } public <T> Maybe<Class<T>> tryResolveClass(String type, OsgiBundleWithUrl... osgiBundles) { return tryResolveClass(type, Arrays.asList(osgiBundles)); } public <T> Maybe<Class<T>> tryResolveClass(String type, Iterable<? extends OsgiBundleWithUrl> osgiBundles) { Map<OsgiBundleWithUrl,Throwable> bundleProblems = MutableMap.of(); Set<String> extraMessages = MutableSet.of(); for (OsgiBundleWithUrl osgiBundle: osgiBundles) { try { Maybe<Bundle> bundle = findBundle(osgiBundle); if (bundle.isPresent()) { Bundle b = bundle.get(); Class<T> clazz; //Extension bundles don't support loadClass. //Instead load from the app classpath. if (EmbeddedFelixFramework.isExtensionBundle(b)) { @SuppressWarnings("unchecked") Class<T> c = (Class<T>)Class.forName(type); clazz = c; } else { @SuppressWarnings("unchecked") Class<T> c = (Class<T>)b.loadClass(type); clazz = c; } return Maybe.of(clazz); } else { bundleProblems.put(osgiBundle, ((Maybe.Absent<?>)bundle).getException()); } } catch (Exception e) { // should come from classloading now; name formatting or missing bundle errors will be caught above Exceptions.propagateIfFatal(e); bundleProblems.put(osgiBundle, e); Throwable cause = e.getCause(); if (cause != null && cause.getMessage().contains("Unresolved constraint in bundle")) { if (BrooklynVersion.INSTANCE.getVersionFromOsgiManifest()==null) { extraMessages.add("No brooklyn-core OSGi manifest available. OSGi will not work."); } if (BrooklynVersion.isDevelopmentEnvironment()) { extraMessages.add("Your development environment may not have created necessary files. Doing a maven build then retrying may fix the issue."); } if (!extraMessages.isEmpty()) log.warn(Strings.join(extraMessages, " ")); log.warn("Unresolved constraint resolving OSGi bundle "+osgiBundle+" to load "+type+": "+cause.getMessage()); if (log.isDebugEnabled()) log.debug("Trace for OSGi resolution failure", e); } } } if (bundleProblems.size()==1) { Throwable error = Iterables.getOnlyElement(bundleProblems.values()); if (error instanceof ClassNotFoundException && error.getCause()!=null && error.getCause().getMessage()!=null) { error = Exceptions.collapseIncludingAllCausalMessages(error); } return Maybe.absent("Unable to resolve class "+type+" in "+Iterables.getOnlyElement(bundleProblems.keySet()) + (extraMessages.isEmpty() ? "" : " ("+Strings.join(extraMessages, " ")+")"), error); } else { return Maybe.absent(Exceptions.create("Unable to resolve class "+type+": "+bundleProblems + (extraMessages.isEmpty() ? "" : " ("+Strings.join(extraMessages, " ")+")"), bundleProblems.values())); } } public Maybe<Bundle> findBundle(OsgiBundleWithUrl catalogBundle) { //Either fail at install time when the user supplied name:version is different //from the one reported from the bundle //or //Ignore user supplied name:version when URL is supplied to be able to find the //bundle even if it's with a different version. // //For now we just log a warning if there's a version discrepancy at install time, //so prefer URL if supplied. BundleFinder bundleFinder = Osgis.bundleFinder(framework); if (catalogBundle.getUrl() != null) { bundleFinder.requiringFromUrl(catalogBundle.getUrl()); } else { bundleFinder.symbolicName(catalogBundle.getSymbolicName()).version(catalogBundle.getVersion()); } return bundleFinder.find(); } /** * Iterates through catalogBundles until one contains a resource with the given name. */ public URL getResource(String name, Iterable<? extends OsgiBundleWithUrl> osgiBundles) { for (OsgiBundleWithUrl osgiBundle: osgiBundles) { try { Maybe<Bundle> bundle = findBundle(osgiBundle); if (bundle.isPresent()) { URL result = bundle.get().getResource(name); if (result!=null) return result; } } catch (Exception e) { Exceptions.propagateIfFatal(e); } } return null; } /** * @return URL's to all resources matching the given name (using {@link Bundle#getResources(String)} in the referenced osgi bundles. */ public Iterable<URL> getResources(String name, Iterable<? extends OsgiBundleWithUrl> osgiBundles) { Set<URL> resources = Sets.newLinkedHashSet(); for (OsgiBundleWithUrl catalogBundle : osgiBundles) { try { Maybe<Bundle> bundle = findBundle(catalogBundle); if (bundle.isPresent()) { Enumeration<URL> result = bundle.get().getResources(name); resources.addAll(Collections.list(result)); } } catch (Exception e) { Exceptions.propagateIfFatal(e); } } return resources; } public Framework getFramework() { return framework; } }