All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.osgi.internal.url.MultiplexingFactory Maven / Gradle / Ivy

There is a newer version: 1.9.22.1
Show newest version
/*******************************************************************************
 * Copyright (c) 2006, 2021 Cognos Incorporated, IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 *******************************************************************************/
package org.eclipse.osgi.internal.url;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.osgi.framework.log.FrameworkLogEntry;
import org.eclipse.osgi.internal.framework.EquinoxBundle;
import org.eclipse.osgi.internal.framework.EquinoxContainer;
import org.eclipse.osgi.storage.StorageUtil;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;

/*
 * An abstract class for handler factory impls (Stream and Content) that can
 * handle environments running multiple osgi frameworks with the same VM.
 */
public abstract class MultiplexingFactory {
	/**
	 * As a short-term (hopefully) solution we use a special class which is defined
	 * using the Unsafe class from the VM.  This class is an implementation of
	 * Collection simply to provide a method add(AccessibleObject)
	 * which turns around and calls AccessibleObject.setAccessible(true).
	 * 

* The reason this is needed is to hack into the VM to get deep reflective access to * the java.net package for the various hacks we have to do to multiplex the * URL and Content handlers. Note that on Java 9 deep reflection is not possible * by default on the java.net package. *

* The setAccessible class will be defined in the java.base module which grants * it the ability to call setAccessible(true) on other types from the java.base module */ public static final Collection setAccessible; static final Collection systemLoaders; static { Collection result = null; try { // Use reflection on Unsafe to avoid having to compile against it Class unsafeClass = Class.forName("sun.misc.Unsafe"); //$NON-NLS-1$ Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe"); //$NON-NLS-1$ // NOTE: deep reflection is allowed on sun.misc package for java 9. theUnsafe.setAccessible(true); Object unsafe = theUnsafe.get(null); // The SetAccessible bytes stored in a resource to avoid real loading of it (see SetAccessible.java.src for source). byte[] bytes = StorageUtil.getBytes(MultiplexingFactory.class.getResource("SetAccessible.bytes").openStream(), -1, 4000); //$NON-NLS-1$ Class> collectionClass = null; // using defineAnonymousClass here because it seems more simple to get what we need try { Method defineAnonymousClass = unsafeClass.getMethod("defineAnonymousClass", Class.class, byte[].class, //$NON-NLS-1$ Object[].class); @SuppressWarnings("unchecked") Class> unchecked = (Class>) defineAnonymousClass .invoke(unsafe, URL.class, bytes, (Object[]) null); collectionClass = unchecked; } catch (NoSuchMethodException e) { long offset = (long) unsafeClass.getMethod("staticFieldOffset", Field.class).invoke(unsafe, //$NON-NLS-1$ MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP")); //$NON-NLS-1$ MethodHandles.Lookup lookup = (MethodHandles.Lookup) unsafeClass .getMethod("getObject", Object.class, long.class) //$NON-NLS-1$ .invoke(unsafe, MethodHandles.Lookup.class, offset); lookup = lookup.in(URL.class); Class classOption = Class.forName("java.lang.invoke.MethodHandles$Lookup$ClassOption"); //$NON-NLS-1$ Object classOptions = Array.newInstance(classOption, 0); Method defineHiddenClass = Lookup.class.getMethod("defineHiddenClass", byte[].class, boolean.class, //$NON-NLS-1$ classOptions.getClass()); lookup = (Lookup) defineHiddenClass.invoke(lookup, bytes, Boolean.FALSE, classOptions); @SuppressWarnings("unchecked") Class> unchecked = (Class>) lookup .lookupClass(); collectionClass = unchecked; } result = collectionClass.getConstructor().newInstance(); } catch (Throwable t) { t.printStackTrace(); // ingore as if there is no Unsafe } setAccessible = result; Collection loaders = new ArrayList<>(); try { ClassLoader cl = ClassLoader.getSystemClassLoader(); while (cl != null) { loaders.add(cl); cl = cl.getParent(); } } catch (Throwable t) { // ignore as if no loaders } systemLoaders = Collections.unmodifiableCollection(loaders); } protected EquinoxContainer container; protected BundleContext context; private List factories; // list of multiplexed factories // used to get access to the protected SecurityManager#getClassContext method static class InternalSecurityManager extends SecurityManager { @Override public Class[] getClassContext() { return super.getClassContext(); } } private static InternalSecurityManager internalSecurityManager = new InternalSecurityManager(); MultiplexingFactory(BundleContext context, EquinoxContainer container) { this.context = context; this.container = container; } abstract public void setParentFactory(Object parentFactory); abstract public Object getParentFactory(); public boolean isMultiplexing() { return getFactories() != null; } public void register(Object factory) { // set parent for each factory so they can do proper delegation try { Class clazz = factory.getClass(); Method setParentFactory = clazz.getMethod("setParentFactory", new Class[] {Object.class}); //$NON-NLS-1$ setParentFactory.invoke(factory, new Object[] {getParentFactory()}); } catch (Exception e) { container.getLogServices().log(MultiplexingFactory.class.getName(), FrameworkLogEntry.ERROR, "register", e); //$NON-NLS-1$ // just return and not have it registered return; } addFactory(factory); } public void unregister(Object factory) { removeFactory(factory); // close the service tracker try { // this is brittle; if class does not directly extend MultplexingFactory then this method will not exist, but we do not want a public method here Method closeTracker = factory.getClass().getSuperclass().getDeclaredMethod("closePackageAdminTracker", (Class[]) null); //$NON-NLS-1$ closeTracker.setAccessible(true); // its a private method closeTracker.invoke(factory, (Object[]) null); } catch (Exception e) { container.getLogServices().log(MultiplexingFactory.class.getName(), FrameworkLogEntry.ERROR, "unregister", e); //$NON-NLS-1$ // just return without blowing up here } } public Object designateSuccessor() { List released = releaseFactories(); // Note that we do this outside of the sync block above. // This is only possible because we do additional locking outside of // this class to ensure no other threads are trying to manipulate the // list of registered factories. See Framework class the following methods: // Framework.installURLStreamHandlerFactory(BundleContext, FrameworkAdaptor) // Framework.installContentHandlerFactory(BundleContext, FrameworkAdaptor) // Framework.uninstallURLStreamHandlerFactory // Framework.uninstallContentHandlerFactory() if (released == null || released.isEmpty()) return getParentFactory(); Object successor = released.remove(0); try { Class clazz = successor.getClass(); Method register = clazz.getMethod("register", new Class[] {Object.class}); //$NON-NLS-1$ for (Object r : released) { register.invoke(successor, new Object[] {r}); } } catch (Exception e) { container.getLogServices().log(MultiplexingFactory.class.getName(), FrameworkLogEntry.ERROR, "designateSuccessor", e); //$NON-NLS-1$ throw new RuntimeException(e.getMessage(), e); } closePackageAdminTracker(); // close tracker return successor; } private void closePackageAdminTracker() { // Do nothing, just here for posterity } public Object findAuthorizedFactory(List> ignoredClasses) { List current = getFactories(); Class[] classStack = internalSecurityManager.getClassContext(); for (Class clazz : classStack) { if (clazz == InternalSecurityManager.class || clazz == MultiplexingFactory.class || ignoredClasses.contains(clazz) || isSystemClass(clazz)) continue; if (hasAuthority(clazz)) return this; if (current == null) continue; for (Object factory : current) { try { Method hasAuthorityMethod = factory.getClass().getMethod("hasAuthority", new Class[] {Class.class}); //$NON-NLS-1$ if (((Boolean) hasAuthorityMethod.invoke(factory, new Object[] {clazz})).booleanValue()) { return factory; } } catch (Exception e) { container.getLogServices().log(MultiplexingFactory.class.getName(), FrameworkLogEntry.ERROR, "findAuthorizedURLStreamHandler-loop", e); //$NON-NLS-1$ // we continue to the next factory here instead of failing } } } // Instead of returning null here, this factory is returned; // This means the root factory may provide protocol handlers for call stacks // that have no classes loaded by an bundle class loader. return this; } private boolean isSystemClass(final Class clazz) { // we want to ignore classes from the system ClassLoader cl = AccessController.doPrivileged((PrivilegedAction) clazz::getClassLoader); return cl == null || systemLoaders.contains(cl); } public boolean hasAuthority(Class clazz) { Bundle b = FrameworkUtil.getBundle(clazz); if (!(b instanceof EquinoxBundle)) { return false; } return (container.getStorage().getModuleContainer() == ((EquinoxBundle) b).getModule().getContainer()); } private synchronized List getFactories() { return factories; } private synchronized List releaseFactories() { if (factories == null) return null; List released = new LinkedList<>(factories); factories = null; return released; } private synchronized void addFactory(Object factory) { List updated = (factories == null) ? new LinkedList<>() : new LinkedList<>(factories); updated.add(factory); factories = updated; } private synchronized void removeFactory(Object factory) { List updated = new LinkedList<>(factories); updated.remove(factory); factories = updated.isEmpty() ? null : updated; } static void setAccessible(AccessibleObject o) { if (setAccessible != null) { setAccessible.add(o); } else { o.setAccessible(true); } } }