blob: bfd94b81bef2c71323ac4051d6d806800170a936 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2009 Heiko Seeberger 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.
*
* Contributors:
* Heiko Seeberger - initial implementation
* Martin Lippert - asynchronous cache writing
* Martin Lippert - caching of generated classes
*******************************************************************************/
package org.eclipse.equinox.weaving.internal.caching;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import org.eclipse.equinox.service.weaving.CacheEntry;
import org.eclipse.equinox.service.weaving.ICachingService;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
/**
* <p>
* {@link ICachingService} instantiated by {@link CachingServiceFactory} for
* each bundle.
* </p>
* <p>
*
* @author Heiko Seeberger
* @author Martin Lippert
*/
public class BundleCachingService implements ICachingService {
private static final int READ_BUFFER_SIZE = 8 * 1024;
private final Bundle bundle;
private File cacheDirectory;
private final String cacheKey;
private final BlockingQueue<CacheItem> cacheWriterQueue;
/**
* @param bundleContext Must not be null!
* @param bundle Must not be null!
* @param key Must not be null!
* @param cacheWriterQueue The queue for items to be written to the cache,
* must not be null
* @throws IllegalArgumentException if given bundleContext or bundle is
* null.
*/
public BundleCachingService(final BundleContext bundleContext,
final Bundle bundle, final String key,
final BlockingQueue<CacheItem> cacheWriterQueue) {
if (bundleContext == null) {
throw new IllegalArgumentException(
"Argument \"bundleContext\" must not be null!"); //$NON-NLS-1$
}
if (bundle == null) {
throw new IllegalArgumentException(
"Argument \"bundle\" must not be null!"); //$NON-NLS-1$
}
if (key == null) {
throw new IllegalArgumentException(
"Argument \"key\" must not be null!"); //$NON-NLS-1$
}
this.bundle = bundle;
this.cacheKey = hashNamespace(key);
this.cacheWriterQueue = cacheWriterQueue;
final File dataFile = bundleContext.getDataFile(cacheKey);
if (dataFile != null) {
final String bundleCacheDir = bundle.getBundleId()
+ "-" + bundle.getLastModified(); //$NON-NLS-1$
cacheDirectory = new File(dataFile, bundleCacheDir);
} else {
Log.error("Cannot initialize cache!", null); //$NON-NLS-1$
}
}
/**
* @see org.eclipse.equinox.service.weaving.ICachingService#canCacheGeneratedClasses()
*/
public boolean canCacheGeneratedClasses() {
return true;
}
/**
* @see org.eclipse.equinox.service.weaving.ICachingService#findStoredClass(java.lang.String,
* java.net.URL, java.lang.String)
*/
public CacheEntry findStoredClass(final String namespace,
final URL sourceFileURL, final String name) {
if (name == null) {
throw new IllegalArgumentException(
"Argument \"name\" must not be null!"); //$NON-NLS-1$
}
byte[] storedClass = null;
boolean isCached = false;
if (cacheDirectory != null) {
final File cachedBytecodeFile = new File(cacheDirectory, name);
storedClass = read(name, cachedBytecodeFile);
isCached = storedClass != null;
}
if (Log.isDebugEnabled()) {
Log.debug(MessageFormat.format("for [{0}]: {1} {2}", bundle //$NON-NLS-1$
.getSymbolicName(), ((storedClass != null) ? "Found" //$NON-NLS-1$
: "Found NOT"), name)); //$NON-NLS-1$
}
return new CacheEntry(isCached, storedClass);
}
/**
* Writes the remaining cache to disk.
*/
public void stop() {
}
/**
* @see org.eclipse.equinox.service.weaving.ICachingService#storeClass(java.lang.String,
* java.net.URL, java.lang.Class, byte[])
*/
public boolean storeClass(final String namespace, final URL sourceFileURL,
final Class<?> clazz, final byte[] classbytes) {
if (clazz == null) {
throw new IllegalArgumentException(
"Argument \"clazz\" must not be null!"); //$NON-NLS-1$
}
if (classbytes == null) {
throw new IllegalArgumentException(
"Argument \"classbytes\" must not be null!"); //$NON-NLS-1$
}
if (cacheDirectory == null) {
return false;
}
final CacheItem item = new CacheItem(classbytes, cacheDirectory
.getAbsolutePath(), clazz.getName());
return this.cacheWriterQueue.offer(item);
}
/**
* @see org.eclipse.equinox.service.weaving.ICachingService#storeClassAndGeneratedClasses(java.lang.String,
* java.net.URL, java.lang.Class, byte[], java.util.Map)
*/
public boolean storeClassAndGeneratedClasses(final String namespace,
final URL sourceFileUrl, final Class<?> clazz,
final byte[] classbytes, final Map<String, byte[]> generatedClasses) {
final CacheItem item = new CacheItem(classbytes, cacheDirectory
.getAbsolutePath(), clazz.getName(), generatedClasses);
return this.cacheWriterQueue.offer(item);
}
/**
* Hash the shared class namespace using MD5
*
* @param keyToHash
* @return the MD5 version of the input string
*/
private String hashNamespace(final String namespace) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5"); //$NON-NLS-1$
} catch (final NoSuchAlgorithmException e) {
e.printStackTrace();
}
final byte[] bytes = md.digest(namespace.getBytes());
final StringBuffer result = new StringBuffer();
for (final byte b : bytes) {
int num;
if (b < 0) {
num = b + 256;
} else {
num = b;
}
String s = Integer.toHexString(num);
while (s.length() < 2) {
s = "0" + s; //$NON-NLS-1$
}
result.append(s);
}
return new String(result);
}
private byte[] read(final String name, final File file) {
int length = (int) file.length();
InputStream in = null;
try {
byte[] classbytes = new byte[length];
int bytesread = 0;
int readcount;
in = new FileInputStream(file);
if (length > 0) {
classbytes = new byte[length];
for (; bytesread < length; bytesread += readcount) {
readcount = in.read(classbytes, bytesread, length
- bytesread);
if (readcount <= 0) /* if we didn't read anything */
break; /* leave the loop */
}
} else /* BundleEntry does not know its own length! */{
length = READ_BUFFER_SIZE;
classbytes = new byte[length];
readloop: while (true) {
for (; bytesread < length; bytesread += readcount) {
readcount = in.read(classbytes, bytesread, length
- bytesread);
if (readcount <= 0) /* if we didn't read anything */
break readloop; /* leave the loop */
}
final byte[] oldbytes = classbytes;
length += READ_BUFFER_SIZE;
classbytes = new byte[length];
System.arraycopy(oldbytes, 0, classbytes, 0, bytesread);
}
}
if (classbytes.length > bytesread) {
final byte[] oldbytes = classbytes;
classbytes = new byte[bytesread];
System.arraycopy(oldbytes, 0, classbytes, 0, bytesread);
}
return classbytes;
} catch (final IOException e) {
Log.debug(MessageFormat.format(
"for [{0}]: Cannot read [1] from cache!", bundle //$NON-NLS-1$
.getSymbolicName(), name));
return null;
} finally {
if (in != null) {
try {
in.close();
} catch (final IOException e) {
Log.error(MessageFormat.format(
"for [{0}]: Cannot close cache file for [1]!", //$NON-NLS-1$
bundle.getSymbolicName(), name), e);
}
}
}
}
}