You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SimpleCache.java 12KB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. package org.aspectj.weaver.tools.cache;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.File;
  4. import java.io.FileInputStream;
  5. import java.io.FileNotFoundException;
  6. import java.io.FileOutputStream;
  7. import java.io.IOException;
  8. import java.io.ObjectInputStream;
  9. import java.io.ObjectOutputStream;
  10. import java.lang.reflect.InvocationTargetException;
  11. import java.lang.reflect.Method;
  12. import java.nio.charset.StandardCharsets;
  13. import java.nio.file.Files;
  14. import java.security.ProtectionDomain;
  15. import java.util.Arrays;
  16. import java.util.Collections;
  17. import java.util.HashMap;
  18. import java.util.Map;
  19. import java.util.Optional;
  20. import java.util.zip.CRC32;
  21. import org.aspectj.weaver.Dump;
  22. import org.aspectj.weaver.tools.Trace;
  23. import org.aspectj.weaver.tools.TraceFactory;
  24. /*******************************************************************************
  25. * Copyright (c) 2012 Contributors.
  26. * All rights reserved.
  27. * This program and the accompanying materials are made available
  28. * under the terms of the Eclipse Public License v 2.0
  29. * which accompanies this distribution and is available at
  30. * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
  31. *
  32. * Contributors:
  33. * Abraham Nevado (lucierna) initial implementation
  34. ********************************************************************************/
  35. public class SimpleCache {
  36. private static final String SAME_BYTES_STRING = "IDEM";
  37. static final byte[] SAME_BYTES = SAME_BYTES_STRING.getBytes(StandardCharsets.UTF_8);
  38. private final Map<String, byte[]> cacheMap;
  39. private boolean enabled = false;
  40. // cache for generated classes
  41. private Map<String, byte[]> generatedCache;
  42. private static final String GENERATED_CACHE_SUBFOLDER = "panenka.cache";
  43. private static final String GENERATED_CACHE_SEPARATOR = ";";
  44. public static final String IMPL_NAME = "shared";
  45. protected SimpleCache(String folder, boolean enabled) {
  46. this.enabled = enabled;
  47. cacheMap = Collections.synchronizedMap(StoreableCachingMap.init(folder));
  48. if (enabled) {
  49. String generatedCachePath = folder + File.separator + GENERATED_CACHE_SUBFOLDER;
  50. File f = new File(generatedCachePath);
  51. if (!f.exists()) {
  52. f.mkdir();
  53. }
  54. generatedCache = Collections.synchronizedMap(StoreableCachingMap.init(generatedCachePath, 0));
  55. }
  56. }
  57. /**
  58. * Get bytes for given class from cache. If necessary, define and initialise the class first.
  59. *
  60. * @param classname name of class to be retrieved from the cache
  61. * @param bytes class bytes (used to calculate cache key)
  62. * @param loader class loader
  63. * @param protectionDomain protection domain
  64. *
  65. * @return {@code null}, if the cache is disabled or if it contains no entry for the given class. An
  66. * {@code Optional<byte[]>} value, if the cache knows about the class. The optional will be empty, if the cache entry
  67. * represents and unwoven class, i.e. its bytes are identical to the original bytes.
  68. */
  69. @SuppressWarnings("OptionalAssignedToNull")
  70. public Optional<byte[]> getAndInitialize(
  71. String classname,
  72. byte[] bytes,
  73. ClassLoader loader,
  74. ProtectionDomain protectionDomain
  75. )
  76. {
  77. if (!enabled) {
  78. // Cache disabled
  79. return null;
  80. }
  81. byte[] res = get(classname, bytes);
  82. if (Arrays.equals(SAME_BYTES, res)) {
  83. // Cache hit: unwoven class
  84. return Optional.empty();
  85. }
  86. if (res != null) {
  87. // Cache hit: woven class
  88. initializeClass(classname, res, loader, protectionDomain);
  89. return Optional.of(res);
  90. }
  91. // Cache miss
  92. return null;
  93. }
  94. private byte[] get(String classname, byte[] bytes) {
  95. String key = generateKey(classname, bytes);
  96. return cacheMap.get(key);
  97. }
  98. public void put(String classname, byte[] origbytes, byte[] wovenbytes) {
  99. if (!enabled) {
  100. return;
  101. }
  102. String key = generateKey(classname, origbytes);
  103. if (wovenbytes == null || Arrays.equals(origbytes, wovenbytes)) {
  104. cacheMap.put(key, SAME_BYTES);
  105. return;
  106. }
  107. cacheMap.put(key, wovenbytes);
  108. }
  109. private String generateKey(String classname, byte[] bytes) {
  110. CRC32 checksum = new CRC32();
  111. checksum.update(bytes);
  112. long crc = checksum.getValue();
  113. classname = classname.replace("/", ".");
  114. return classname + "-" + crc;
  115. }
  116. private static class StoreableCachingMap extends HashMap {
  117. // TODO: This class extends a raw HashMap, but instances of this class are assigned to fields
  118. // Map<String, byte[]> cacheMap and Map<String, byte[]> generatedCache without casts. However, we cannot
  119. // simply declare 'extends HashMap<String, byte[]>', because 'put' writes String values (paths) when given
  120. // byte[] ones, while 'get' geturns byte[] ones, which is inconsistent. I.e., superficially the class behaves
  121. // like a Map<String, byte[]>, while not really being one. This is ugly and hard to understand.
  122. private final String folder;
  123. private static final String CACHENAMEIDX = "cache.idx";
  124. private long lastStored = System.currentTimeMillis();
  125. private static final int DEF_STORING_TIMER = 60000; //ms
  126. private final int storingTimer;
  127. private transient Trace trace;
  128. private void initTrace() {
  129. trace = TraceFactory.getTraceFactory().getTrace(StoreableCachingMap.class);
  130. }
  131. private StoreableCachingMap(String folder, int storingTimer) {
  132. this.folder = folder;
  133. initTrace();
  134. this.storingTimer = storingTimer;
  135. }
  136. public static StoreableCachingMap init(String folder) {
  137. return init(folder, DEF_STORING_TIMER);
  138. }
  139. public static StoreableCachingMap init(String folder, int storingTimer) {
  140. File file = new File(folder + File.separator + CACHENAMEIDX);
  141. if (file.exists()) {
  142. try (ObjectInputStream in = new ObjectInputStream(Files.newInputStream(file.toPath()))) {
  143. // Deserialize the object
  144. StoreableCachingMap sm = (StoreableCachingMap) in.readObject();
  145. sm.initTrace();
  146. return sm;
  147. }
  148. catch (Exception e) {
  149. Trace trace = TraceFactory.getTraceFactory().getTrace(StoreableCachingMap.class);
  150. trace.error("Error reading Storable Cache", e);
  151. }
  152. }
  153. return new StoreableCachingMap(folder, storingTimer);
  154. }
  155. @Override
  156. public Object get(Object obj) {
  157. try {
  158. if (super.containsKey(obj)) {
  159. String path = (String) super.get(obj);
  160. if (path.equals(SAME_BYTES_STRING)) {
  161. return SAME_BYTES;
  162. }
  163. return readFromPath(path);
  164. }
  165. else {
  166. return null;
  167. }
  168. }
  169. catch (IOException e) {
  170. trace.error("Error reading key:" + obj.toString(), e);
  171. Dump.dumpWithException(e);
  172. }
  173. return null;
  174. }
  175. @Override
  176. public Object put(Object key, Object value) {
  177. try {
  178. String path;
  179. byte[] valueBytes = (byte[]) value;
  180. if (Arrays.equals(valueBytes, SAME_BYTES)) {
  181. path = SAME_BYTES_STRING;
  182. }
  183. else {
  184. path = writeToPath((String) key, valueBytes);
  185. }
  186. Object result = super.put(key, path);
  187. storeMap();
  188. return result;
  189. }
  190. catch (IOException e) {
  191. trace.error("Error inserting in cache: key:" + key + "; value:" + value.toString(), e);
  192. Dump.dumpWithException(e);
  193. }
  194. return null;
  195. }
  196. public void storeMap() {
  197. long now = System.currentTimeMillis();
  198. if ((now - lastStored) < storingTimer) {
  199. return;
  200. }
  201. File file = new File(folder + File.separator + CACHENAMEIDX);
  202. try (ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(file.toPath()))) {
  203. // Deserialize the object
  204. out.writeObject(this);
  205. lastStored = now;
  206. }
  207. catch (Exception e) {
  208. trace.error("Error storing cache; cache file:" + file.getAbsolutePath(), e);
  209. Dump.dumpWithException(e);
  210. }
  211. }
  212. private byte[] readFromPath(String fullPath) throws IOException {
  213. try (
  214. FileInputStream is = new FileInputStream(fullPath);
  215. ByteArrayOutputStream buffer = new ByteArrayOutputStream()
  216. ) {
  217. int nRead;
  218. byte[] data = new byte[16384];
  219. while ((nRead = is.read(data, 0, data.length)) != -1) {
  220. buffer.write(data, 0, nRead);
  221. }
  222. buffer.flush();
  223. return buffer.toByteArray();
  224. }
  225. catch (FileNotFoundException e) {
  226. // May be caused by a generated class that has been stored in generated cache but not saved in cache folder
  227. System.out.println("FileNotFoundExceptions: The aspectj cache is corrupt. Please clean it and reboot the server. Cache path:" + this.folder);
  228. e.printStackTrace();
  229. return null;
  230. }
  231. }
  232. private String writeToPath(String key, byte[] bytes) throws IOException {
  233. String fullPath = folder + File.separator + key;
  234. try (FileOutputStream fos = new FileOutputStream(fullPath)) {
  235. fos.write(bytes);
  236. fos.flush();
  237. }
  238. return fullPath;
  239. }
  240. }
  241. private void initializeClass(
  242. String className, byte[] bytes,
  243. ClassLoader loader, ProtectionDomain protectionDomain
  244. )
  245. {
  246. String[] generatedClassesNames = getGeneratedClassesNames(className, bytes);
  247. if (generatedClassesNames == null) {
  248. return;
  249. }
  250. for (String generatedClassName : generatedClassesNames) {
  251. byte[] generatedBytes = get(generatedClassName, bytes);
  252. if (protectionDomain == null) {
  253. defineClass(loader, generatedClassName, generatedBytes);
  254. }
  255. else {
  256. defineClass(loader, generatedClassName, generatedBytes, protectionDomain);
  257. }
  258. }
  259. }
  260. private String[] getGeneratedClassesNames(String className, byte[] bytes) {
  261. String key = generateKey(className, bytes);
  262. byte[] readBytes = generatedCache.get(key);
  263. if (readBytes == null) {
  264. return null;
  265. }
  266. String readString = new String(readBytes);
  267. return readString.split(GENERATED_CACHE_SEPARATOR);
  268. }
  269. public void addGeneratedClassesNames(String parentClassName, byte[] parentBytes, String generatedClassName) {
  270. if (!enabled) {
  271. return;
  272. }
  273. String key = generateKey(parentClassName, parentBytes);
  274. byte[] storedBytes = generatedCache.get(key);
  275. if (storedBytes == null) {
  276. generatedCache.put(key, generatedClassName.getBytes());
  277. }
  278. else {
  279. String storedClasses = new String(storedBytes);
  280. storedClasses += GENERATED_CACHE_SEPARATOR + generatedClassName;
  281. generatedCache.put(key, storedClasses.getBytes());
  282. }
  283. }
  284. private Method defineClassMethod = null;
  285. private Method defineClassWithProtectionDomainMethod = null;
  286. private void defineClass(ClassLoader loader, String name, byte[] bytes) {
  287. try {
  288. if (defineClassMethod == null) {
  289. // TODO: Replace by class definition strategy used in ClassLoaderWeavingAdaptor
  290. defineClassMethod = ClassLoader.class.getDeclaredMethod(
  291. "defineClass",
  292. String.class, bytes.getClass(), int.class, int.class
  293. );
  294. defineClassMethod.setAccessible(true);
  295. }
  296. defineClassMethod.invoke(loader, name, bytes, 0, bytes.length);
  297. }
  298. catch (InvocationTargetException e) {
  299. if (e.getTargetException() instanceof LinkageError) {
  300. e.printStackTrace();
  301. }
  302. else {
  303. System.out.println("define generated class failed" + e.getTargetException());
  304. }
  305. }
  306. catch (Exception e) {
  307. e.printStackTrace();
  308. Dump.dumpWithException(e);
  309. }
  310. }
  311. private void defineClass(ClassLoader loader, String name, byte[] bytes, ProtectionDomain protectionDomain) {
  312. try {
  313. if (defineClassWithProtectionDomainMethod == null) {
  314. // TODO: Replace by class definition strategy used in ClassLoaderWeavingAdaptor
  315. defineClassWithProtectionDomainMethod = ClassLoader.class.getDeclaredMethod(
  316. "defineClass",
  317. String.class, bytes.getClass(), int.class, int.class, ProtectionDomain.class
  318. );
  319. defineClassWithProtectionDomainMethod.setAccessible(true);
  320. }
  321. defineClassWithProtectionDomainMethod.invoke(loader, name, bytes, 0, bytes.length, protectionDomain);
  322. }
  323. catch (InvocationTargetException e) {
  324. if (e.getTargetException() instanceof LinkageError) {
  325. e.printStackTrace();
  326. // is already defined (happens for X$ajcMightHaveAspect
  327. // interfaces since aspects are reweaved)
  328. // TODO maw I don't think this is OK and
  329. }
  330. else {
  331. e.printStackTrace();
  332. }
  333. }
  334. catch (NullPointerException e) {
  335. System.out.println("NullPointerException loading class:" + name + ". Probabily caused by a corruput cache. Please clean it and reboot the server");
  336. }
  337. catch (Exception e) {
  338. e.printStackTrace();
  339. Dump.dumpWithException(e);
  340. }
  341. }
  342. }