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

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. }