blob: 2166d275a16dfd0901255328e92314b6fa4917ee [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2018 IBM Corp. 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/
* or the Apache License, Version 2.0 which accompanies this distribution and
* is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* This Source Code may also be made available under the following
* Secondary Licenses when the conditions for such availability set
* forth in the Eclipse Public License, v. 2.0 are satisfied: GNU
* General Public License, version 2 with the GNU Classpath
* Exception [1] and GNU General Public License, version 2 with the
* OpenJDK Assembly Exception [2].
*
* [1] https://www.gnu.org/software/classpath/license.html
* [2] http://openjdk.java.net/legal/assembly-exception.html
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception
*******************************************************************************/
package org.eclipse.osgi.internal.cds;
import com.ibm.oti.shared.HelperAlreadyDefinedException;
import com.ibm.oti.shared.Shared;
import com.ibm.oti.shared.SharedClassHelperFactory;
import com.ibm.oti.shared.SharedClassURLHelper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import org.eclipse.osgi.internal.hookregistry.BundleFileWrapperFactoryHook;
import org.eclipse.osgi.internal.hookregistry.ClassLoaderHook;
import org.eclipse.osgi.internal.hookregistry.HookRegistry;
import org.eclipse.osgi.internal.loader.ModuleClassLoader;
import org.eclipse.osgi.internal.loader.classpath.ClasspathEntry;
import org.eclipse.osgi.internal.loader.classpath.ClasspathManager;
import org.eclipse.osgi.storage.BundleInfo.Generation;
import org.eclipse.osgi.storage.bundlefile.BundleEntry;
import org.eclipse.osgi.storage.bundlefile.BundleFile;
import org.eclipse.osgi.storage.bundlefile.BundleFileWrapper;
import org.eclipse.osgi.storage.bundlefile.BundleFileWrapperChain;
public class CDSHookImpls extends ClassLoaderHook implements BundleFileWrapperFactoryHook {
private static SharedClassHelperFactory factory = Shared.getSharedClassHelperFactory();
private static java.lang.reflect.Method minimizeMethod = null;
private static boolean hasMinimizeMethod = true; /* Assume true to begin with */
// With Equinox bug 226038 (v3.4), the framework will now pass an instance
// of BundleFileWrapperChain rather than the wrapped BundleFile. This is
// so that multiple wrapping hooks can each wrap the BundleFile and all
// wrappers are accessible.
//
// The Wrapper chain will look like below:
// WrapperChain -> Wrapper<N> -> WrapperChain -> CDSBundleFile -> WrapperChain -> BundleFile
//
private static CDSBundleFile getCDSBundleFile(BundleFile bundleFile) {
CDSBundleFile cdsBundleFile = null;
if (bundleFile instanceof BundleFileWrapperChain) {
// Equinox > 3.4
BundleFile wrapped = null;
do {
wrapped = ((BundleFileWrapperChain) bundleFile).getWrapped();
if (wrapped instanceof CDSBundleFile) {
cdsBundleFile = (CDSBundleFile) wrapped;
break;
}
//Go to next wrapper chain.
bundleFile = ((BundleFileWrapperChain) bundleFile).getNext();
} while (wrapped != null);
}
return cdsBundleFile;
}
public void recordClassDefine(String name, Class<?> clazz, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager manager) { // only attempt to record the class define if:
// 1) the class was found (clazz != null)
// 2) the class has the magic class number CAFEBABE indicating a real class
// 3) the bundle file for the classpath entry is of type CDSBundleFile
// 4) class bytes is same as passed to weaving hook i.e. weaving hook did not modify the class bytes
if ((null == clazz) || (false == hasMagicClassNumber(classbytes)) || (null == getCDSBundleFile(classpathEntry.getBundleFile()))) {
return;
}
try {
// check if weaving hook modified the class bytes
byte originalClassBytes[] = entry.getBytes();
if (originalClassBytes != classbytes) {
// weaving hook has potentially modified the class bytes
boolean modified = false;
if (originalClassBytes.length == classbytes.length) {
// do a byte-by-byte comparison
modified = !Arrays.equals(classbytes, originalClassBytes);
} else {
modified = true;
}
if (modified) {
// Class bytes have been modified by weaving hooks.
// Such classes need to be stored as Orphans, so skip the call to storeSharedClass()
return;
}
}
} catch (IOException e) {
// this should never happen, but in case it does, its safe to return
return;
}
CDSBundleFile cdsFile = getCDSBundleFile(classpathEntry.getBundleFile());
if (null == cdsFile.getURL()) {
// something went wrong trying to determine the url to the real bundle file
return;
}
// look for the urlHelper; if it does not exist then we are not sharing for this class loader
SharedClassURLHelper urlHelper = cdsFile.getURLHelper();
if (urlHelper == null) {
// this should never happen but just in case get the helper from the base host bundle file.
CDSBundleFile hostBundleFile = getCDSBundleFile(manager.getGeneration().getBundleFile());
if (null != hostBundleFile) {
// try getting the helper from the host base cdsFile
urlHelper = hostBundleFile.getURLHelper();
}
if (null != urlHelper) {
cdsFile.setURLHelper(urlHelper);
}
}
if (null != urlHelper) {
// store the class in the cache
urlHelper.storeSharedClass(null, cdsFile.getURL(), clazz);
cdsFile.setPrimed(true);
}
}
/* Calling setMinimizeUpdateChecks() on the urlHelper tells it to only check the plugin jar for updates
* once on startup. This removes the need to "prime" plugins by always cacheing the first class from the jar.
*
* Java5 does not have a runMinimizeUpdateChecks method, but Java6 does. The text below explains why.
*
* Java6 has an improved jar update detection mechanism which is event-driven and listens for
* real jar open and close events. It will check jar timestamps on every class-load for closed jars (when
* loading cached classes from those jars) and not check them if it knows the jars are open.
*
* Java5 didn't know about jar open/close events so simply assumed that the first class to be stored by
* a plugin implied that its jar was opened indefinitely. This is why it helps to "prime" a plugin when
* running under Java5 - by storing a class, the jar is opened and the JVM stops checking its timestamp
* which results in faster startup.
*
* While the Java6 behaviour is more correct (it will pick up changes if a jar is closed after having been opened),
* if the jars are not opened or "primed", then it will perform constant checks on their timestamps which hurts startup times.
* This is why setMinimizeUpdateChecks was introduced - it's a means of saying to the urlHelper - regardless of
* whether my container(s) is open or closed, I only want you to check it once for updates.
*
* The consequence of this is that less file handles are open on startup in Java6.
*
* This has been written in such a way that this adaptor will continue to work exactly the same with Java5, but
* will adapt its behaviour when used with Java6 to do the right thing.
*/
private boolean runMinimizeMethod(SharedClassURLHelper urlHelper) {
if (hasMinimizeMethod && (urlHelper != null)) {
if (minimizeMethod == null) {
hasMinimizeMethod = false; /* Assume failure - prove success below */
try {
Class<?> c = urlHelper.getClass();
minimizeMethod = c.getMethod("setMinimizeUpdateChecks"); //$NON-NLS-1$
minimizeMethod.setAccessible(true);
hasMinimizeMethod = true;
} catch (Exception e) {
/* hasMinimizeMethod will be false and we won't try this again */
}
}
if (minimizeMethod != null) {
try {
minimizeMethod.invoke(urlHelper);
return true;
} catch (Exception e) {
hasMinimizeMethod = false;
}
}
}
return false;
}
private boolean hasMagicClassNumber(byte[] classbytes) {
if (classbytes == null || classbytes.length < 4)
return false;
// TODO maybe there is a better way to do this? I'm not sure why I had to AND each byte with the value I was checking ...
return (classbytes[0] & 0xCA) == 0xCA && (classbytes[1] & 0xFE) == 0xFE && (classbytes[2] & 0xBA) == 0xBA && (classbytes[3] & 0xBE) == 0xBE;
}
public void classLoaderCreated(ModuleClassLoader classLoader) {
// try to get the url helper for this class loader
if (factory == null) {
return;
}
CDSBundleFile hostFile = null;
try {
SharedClassURLHelper urlHelper = factory.getURLHelper(classLoader);
boolean minimizeSucceeded = runMinimizeMethod(urlHelper);
// set the url helper for the host base CDSBundleFile
hostFile = getCDSBundleFile(classLoader.getClasspathManager().getGeneration().getBundleFile());
if (hostFile != null) {
hostFile.setURLHelper(urlHelper);
if (minimizeSucceeded) {
/* In Java6, there is no longer a requirement to "prime" plugins */
hostFile.setPrimed(true);
}
}
} catch (HelperAlreadyDefinedException e) {
// We should never get here.
// If we do, we simply won't share for this ClassLoader
}
}
public boolean addClassPathEntry(ArrayList<ClasspathEntry> cpEntries, String cp, ClasspathManager hostmanager, Generation sourceGeneration) {
CDSBundleFile hostFile = getCDSBundleFile(hostmanager.getGeneration().getBundleFile());
CDSBundleFile sourceFile = getCDSBundleFile(sourceGeneration.getBundleFile());
if ((hostFile != sourceFile) && (null != hostFile) && (null != sourceFile)) {
// set the helper that got set on the host base bundle file in initializedClassLoader
SharedClassURLHelper urlHelper = hostFile.getURLHelper();
sourceFile.setURLHelper(urlHelper);
}
return false;
}
//////////////// BundleFileWrapperFactoryHook //////////////
public BundleFileWrapper wrapBundleFile(BundleFile bundleFile, Generation generation, boolean base) {
// wrap the real bundle file for purposes of loading shared classes.
CDSBundleFile newBundleFile;
if (!base && generation.getBundleInfo().getBundleId() != 0) {
// initialize the urlHelper from the base one.
SharedClassURLHelper urlHelper = null;
BundleFile baseFile = generation.getBundleFile();
if ((baseFile = getCDSBundleFile(baseFile)) != null) {
urlHelper = ((CDSBundleFile) baseFile).getURLHelper();
}
newBundleFile = new CDSBundleFile(bundleFile, urlHelper);
} else {
newBundleFile = new CDSBundleFile(bundleFile);
}
return newBundleFile;
}
void registerHooks(HookRegistry hookRegistry) {
// only register if sharing is enabled
if (!Shared.isSharingEnabled()) {
return;
}
hookRegistry.addClassLoaderHook(this);
hookRegistry.addBundleFileWrapperFactoryHook(this);
}
}