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

org.aspectj.weaver.tools.cache.AsynchronousFileCacheBacking Maven / Gradle / Ivy

Go to download

AspectJ tools most notably contains the AspectJ compiler (AJC). AJC applies aspects to Java classes during compilation, fully replacing Javac for plain Java classes and also compiling native AspectJ or annotation-based @AspectJ syntax. Furthermore, AJC can weave aspects into existing class files in a post-compile binary weaving step. This library is a superset of AspectJ weaver and hence also of AspectJ runtime.

There is a newer version: 1.9.22.1
Show newest version
/*******************************************************************************
 * Copyright (c) 2012 Contributors.
 * All rights reserved.
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Public License v 2.0
 * which accompanies this distribution and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
 *
 * Contributors:
 *   Lyor Goldstein (vmware)	add support for weaved class being re-defined
 *******************************************************************************/

package org.aspectj.weaver.tools.cache;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;

import org.aspectj.util.FileUtil;
import org.aspectj.util.LangUtil;
import org.aspectj.weaver.tools.Trace;
import org.aspectj.weaver.tools.TraceFactory;

/**
 * Uses a background thread to do the actual I/O and for caching "persistence"
 * so that the caching works faster on repeated activations of the application.
 * The class maintains an in-memory cache, and uses a queue of {@link AsyncCommand}s
 * to signal to a background thread various actions required to "synchronize"
 * the in-memory cache with the persisted copy. Whenever there is a cache miss
 * from the {@link #get(CachedClassReference, byte[])} call, the weaver issues a
 * {@link #put(CachedClassEntry, byte[])} call. This call has 2 side-effects:
 * 
    *
  • * The in-memory cache is updated so that subsequent calls to {@link #get(CachedClassReference, byte[])} * will not return the mapped value. *
  • * *
  • * An "update index" {@link AsyncCommand} is posted to the background * thread so that the newly mapped value will be persisted (eventually) *
  • *
* The actual persistence is implemented by the concrete classes */ public abstract class AsynchronousFileCacheBacking extends AbstractIndexedFileCacheBacking { private static final BlockingQueue commandsQ= new LinkedBlockingQueue<>(); private static final ExecutorService execService=Executors.newSingleThreadExecutor(); private static Future commandsRunner; protected final Map index, exposedIndex; protected final Map bytesMap, exposedBytes; protected AsynchronousFileCacheBacking (File cacheDir) { super(cacheDir); index = readIndex(cacheDir, getIndexFile()); exposedIndex = Collections.unmodifiableMap(index); bytesMap = readClassBytes(index, cacheDir); exposedBytes = Collections.unmodifiableMap(bytesMap); } @Override protected Map getIndex() { return index; } public CachedClassEntry get(CachedClassReference ref, byte[] originalBytes) { String key=ref.getKey(); final IndexEntry indexEntry; synchronized(index) { if ((indexEntry=index.get(key)) == null) { return null; } } if (crc(originalBytes) != indexEntry.crcClass) { if ((logger != null) && logger.isTraceEnabled()) { logger.debug("get(" + getCacheDirectory() + ") mismatched original class bytes CRC for " + key); } remove(key); return null; } if (indexEntry.ignored) { return new CachedClassEntry(ref, WeavedClassCache.ZERO_BYTES, CachedClassEntry.EntryType.IGNORED); } final byte[] bytes; synchronized(bytesMap) { /* * NOTE: we assume that keys represent classes so if we have their * bytes they will not be re-created */ if ((bytes=bytesMap.remove(key)) == null) { return null; } } if (indexEntry.generated) { return new CachedClassEntry(ref, bytes, CachedClassEntry.EntryType.GENERATED); } else { return new CachedClassEntry(ref, bytes, CachedClassEntry.EntryType.WEAVED); } } public void put(CachedClassEntry entry, byte[] originalBytes) { String key=entry.getKey(); byte[] bytes=entry.isIgnored() ? null : entry.getBytes(); synchronized(index) { IndexEntry indexEntry=index.get(key); if (indexEntry != null) { return; } /* * Note: we do not cache the class bytes - only send them to * be saved. The assumption is that the 'put' call was invoked * because 'get' failed to return any bytes. And since we assume * that each class bytes are required only once, there is no * need to cache them */ indexEntry = createIndexEntry(entry, originalBytes); index.put(key, indexEntry); } if (!postCacheCommand(new InsertCommand(this, key, bytes))) { if ((logger != null) && logger.isTraceEnabled()) { logger.error("put(" + getCacheDirectory() + ") Failed to post insert command for " + key); } } if ((logger != null) && logger.isTraceEnabled()) { logger.debug("put(" + getCacheDirectory() + ")[" + key + "] inserted"); } } public void remove(CachedClassReference ref) { remove(ref.getKey()); } protected IndexEntry remove (String key) { IndexEntry entry; synchronized(index) { entry = index.remove(key); } synchronized(bytesMap) { bytesMap.remove(key); } if (!postCacheCommand(new RemoveCommand(this, key))) { if ((logger != null) && logger.isTraceEnabled()) { logger.error("remove(" + getCacheDirectory() + ") Failed to post remove command for " + key); } } if (entry != null) { if (!key.equals(entry.key)) { if ((logger != null) && logger.isTraceEnabled()) { logger.error("remove(" + getCacheDirectory() + ") Mismatched keys: " + key + " / " + entry.key); } } else if ((logger != null) && logger.isTraceEnabled()) { logger.debug("remove(" + getCacheDirectory() + ")[" + key + "] removed"); } } return entry; } public List getIndexEntries () { synchronized(index) { if (index.isEmpty()) { return Collections.emptyList(); } else { return new ArrayList<>(index.values()); } } } public Map getIndexMap () { return exposedIndex; } public Map getBytesMap () { return exposedBytes; } public void clear() { synchronized(index) { index.clear(); } if (!postCacheCommand(new ClearCommand(this))) { if ((logger != null) && logger.isTraceEnabled()) { logger.error("Failed to post clear command for " + getIndexFile()); } } } protected void executeCommand (AsyncCommand cmd) throws Exception { if (cmd instanceof ClearCommand) { executeClearCommand(); } else if (cmd instanceof UpdateIndexCommand) { executeUpdateIndexCommand(); } else if (cmd instanceof InsertCommand) { executeInsertCommand((InsertCommand) cmd); } else if (cmd instanceof RemoveCommand) { executeRemoveCommand((RemoveCommand) cmd); } else { throw new UnsupportedOperationException("Unknown command: " + cmd); } } protected void executeClearCommand () throws Exception { FileUtil.deleteContents(getIndexFile()); FileUtil.deleteContents(getCacheDirectory()); } protected void executeUpdateIndexCommand () throws Exception { writeIndex(getIndexFile(), getIndexEntries()); } protected void executeInsertCommand (InsertCommand cmd) throws Exception { writeIndex(getIndexFile(), getIndexEntries()); byte[] bytes=cmd.getClassBytes(); if (bytes != null) { writeClassBytes(cmd.getKey(), bytes); } } protected void executeRemoveCommand (RemoveCommand cmd) throws Exception { Exception err=null; try { removeClassBytes(cmd.getKey()); } catch(Exception e) { err = e; } writeIndex(getIndexFile(), getIndexEntries()); if (err != null) { throw err; // check if the class bytes remove had any problems } } /** * Helper for {@link #executeRemoveCommand(RemoveCommand)} * @param key The key representing the class whose bytes are to be removed * @throws Exception if failed to remove class bytes */ protected abstract void removeClassBytes (String key) throws Exception; protected abstract Map readClassBytes (Map indexMap, File cacheDir); @Override public String toString() { return getClass().getSimpleName() + "[" + String.valueOf(getCacheDirectory()) + "]"; } protected static final T createBacking ( File cacheDir, AsynchronousFileCacheBackingCreator creator) { final Trace trace=TraceFactory.getTraceFactory().getTrace(AsynchronousFileCacheBacking.class); if (!cacheDir.exists()) { if (!cacheDir.mkdirs()) { if ((trace != null) && trace.isTraceEnabled()) { trace.error("Unable to create cache directory at " + cacheDir.getAbsolutePath()); } return null; } } if (!cacheDir.canWrite()) { if ((trace != null) && trace.isTraceEnabled()) { trace.error("Cache directory is not writable at " + cacheDir.getAbsolutePath()); } return null; } // start the service (if needed) only if successfully create the backing instance T backing=creator.create(cacheDir); synchronized(execService) { if (commandsRunner == null) { commandsRunner = execService.submit(new Runnable() { @SuppressWarnings("synthetic-access") public void run() { for ( ; ; ) { try { AsyncCommand cmd=commandsQ.take(); try { AsynchronousFileCacheBacking cache=cmd.getCache(); cache.executeCommand(cmd); } catch(Exception e) { if ((trace != null) && trace.isTraceEnabled()) { trace.error("Failed (" + e.getClass().getSimpleName() + ")" + " to execute " + cmd + ": " + e.getMessage(), e); } } } catch(InterruptedException e) { if ((trace != null) && trace.isTraceEnabled()) { trace.warn("Interrupted"); } Thread.currentThread().interrupt(); break; } } } }); } } // fire-up an update-index command in case index was changed by the constructor if (!postCacheCommand(new UpdateIndexCommand(backing))) { if ((trace != null) && trace.isTraceEnabled()) { trace.warn("Failed to offer update index command to " + cacheDir.getAbsolutePath()); } } return backing; } public static final boolean postCacheCommand (AsyncCommand cmd) { return commandsQ.offer(cmd); } public interface AsynchronousFileCacheBackingCreator { T create (File cacheDir); } /** * Represents an asynchronous command that can be sent to the * {@link AsynchronousFileCacheBacking} instance to be executed * on it asynchronously */ public interface AsyncCommand { /** * @return The {@link AsynchronousFileCacheBacking} on which * this command is supposed to be executed * @see AsynchronousFileCacheBacking#executeCommand(AsyncCommand) */ AsynchronousFileCacheBacking getCache (); } public static abstract class AbstractCommand implements AsyncCommand { private final AsynchronousFileCacheBacking cache; protected AbstractCommand (AsynchronousFileCacheBacking backing) { if ((cache=backing) == null) { throw new IllegalStateException("No backing cache specified"); } } public final AsynchronousFileCacheBacking getCache () { return cache; } @Override public String toString() { return getClass().getSimpleName() + "[" + getCache() + "]"; } } public static class ClearCommand extends AbstractCommand { public ClearCommand (AsynchronousFileCacheBacking cache) { super(cache); } } public static class UpdateIndexCommand extends AbstractCommand { public UpdateIndexCommand (AsynchronousFileCacheBacking cache) { super(cache); } } /** * Base class for {@link AbstractCommand}s that refer to a cache key */ public static abstract class KeyedCommand extends AbstractCommand { private final String key; protected KeyedCommand (AsynchronousFileCacheBacking cache, String keyValue) { super(cache); if (LangUtil.isEmpty(keyValue)) { throw new IllegalStateException("No key value"); } key = keyValue; } public final String getKey () { return key; } @Override public String toString() { return super.toString() + "[" + getKey() + "]"; } } public static class RemoveCommand extends KeyedCommand { public RemoveCommand (AsynchronousFileCacheBacking cache, String keyValue) { super(cache, keyValue); } } public static class InsertCommand extends KeyedCommand { private final byte[] bytes; public InsertCommand (AsynchronousFileCacheBacking cache, String keyValue, byte[] classBytes) { super(cache, keyValue); bytes = classBytes; } public final byte[] getClassBytes () { return bytes; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy