| /******************************************************************************* |
| * Copyright (c) 2008, 2009 Heiko Seeberger and others. |
| * 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: |
| * 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); |
| } |
| } |
| } |
| } |
| |
| } |