001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau; 014 015import static org.apache.juneau.internal.ClassUtils.*; 016import java.util.*; 017import java.util.concurrent.*; 018 019import org.apache.juneau.annotation.*; 020import org.apache.juneau.reflect.*; 021 022/** 023 * Stores a cache of {@link Context} instances mapped by the property stores used to create them. 024 * 025 * <p> 026 * The purpose of this class is to reuse instances of bean contexts, serializers, and parsers when they're being 027 * re-created with previously-used property stores. 028 * 029 * <p> 030 * Since serializers and parsers are immutable and thread-safe, we reuse them whenever possible. 031 */ 032@SuppressWarnings("unchecked") 033public class ContextCache { 034 035 /** 036 * Reusable cache instance. 037 */ 038 public static final ContextCache INSTANCE = new ContextCache(); 039 040 private final static boolean USE_DEEP_MATCHING = Boolean.getBoolean("ContextCache.useDeepMatching"); 041 042 private final ConcurrentHashMap<Class<?>,ConcurrentHashMap<Integer,CacheEntry>> contextCache = new ConcurrentHashMap<>(); 043 private final ConcurrentHashMap<Class<?>,String[]> prefixCache = new ConcurrentHashMap<>(); 044 045 // When enabled, this will spit out cache-hit metrics to the console on shutdown. 046 private static final boolean TRACK_CACHE_HITS = Boolean.getBoolean("juneau.trackCacheHits"); 047 static final Map<String,CacheHit> CACHE_HITS = new ConcurrentHashMap<>(); 048 static { 049 if (TRACK_CACHE_HITS) { 050 Runtime.getRuntime().addShutdownHook( 051 new Thread() { 052 @Override 053 public void run() { 054 int creates=0, cached=0; 055 System.out.println("Cache Hits: [CacheObject] = [numCreated,numCached,cacheHitPercentage]"); 056 for (Map.Entry<String,CacheHit> e : CACHE_HITS.entrySet()) { 057 CacheHit ch = e.getValue(); 058 System.out.println("["+e.getKey()+"] = ["+ch.creates+","+ch.cached+","+((ch.cached*100)/(ch.creates+ch.cached))+"%]"); 059 creates += ch.creates; 060 cached += ch.cached; 061 } 062 if (creates + cached > 0) 063 System.out.println("[total] = ["+creates+","+cached+","+((cached*100)/(creates+cached))+"%]"); 064 } 065 } 066 ); 067 } 068 } 069 070 static void logCache(Class<?> contextClass, boolean wasCached) { 071 if (TRACK_CACHE_HITS) { 072 synchronized(ContextCache.class) { 073 String c = contextClass.getSimpleName(); 074 CacheHit ch = CACHE_HITS.get(c); 075 if (ch == null) 076 ch = new CacheHit(); 077 if (wasCached) 078 ch.cached++; 079 else 080 ch.creates++; 081 ch = CACHE_HITS.put(c, ch); 082 } 083 } 084 } 085 086 static class CacheHit { 087 public int creates, cached; 088 } 089 090 ContextCache() {} 091 092 /** 093 * Creates a new instance of the specified context-based class, or an existing instance if one with the same 094 * property store was already created. 095 * 096 * @param c The instance of the class to create. 097 * @param ps The property store to use to create the class. 098 * @return The 099 */ 100 public <T extends Context> T create(Class<T> c, PropertyStore ps) { 101 String[] prefixes = getPrefixes(c); 102 103 if (prefixes == null) 104 return instantiate(c, ps); 105 106 ConcurrentHashMap<Integer,CacheEntry> m = getContextCache(c); 107 108 Integer hashCode = ps.hashCode(prefixes); 109 CacheEntry ce = m.get(hashCode); 110 111 if (ce != null && USE_DEEP_MATCHING && ! ce.ps.equals(ps)) 112 throw new ContextRuntimeException("Property store hashcode mismatch!", new Object[0]); 113 114 logCache(c, ce != null); 115 116 if (ce == null) { 117 ce = new CacheEntry(ps, instantiate(c, ps)); 118 m.putIfAbsent(hashCode, ce); 119 } 120 121 return (T)ce.context; 122 } 123 124 private <T extends Context> T instantiate(Class<T> c, PropertyStore ps) { 125 try { 126 return newInstance(c, ps); 127 } catch (ContextRuntimeException e) { 128 throw e; 129 } catch (Exception e) { 130 throw new ContextRuntimeException(e, "Could not create instance of class ''{0}''", c); 131 } 132 } 133 134 private ConcurrentHashMap<Integer,CacheEntry> getContextCache(Class<?> c) { 135 ConcurrentHashMap<Integer,CacheEntry> m = contextCache.get(c); 136 if (m == null) { 137 m = new ConcurrentHashMap<>(); 138 ConcurrentHashMap<Integer,CacheEntry> m2 = contextCache.putIfAbsent(c, m); 139 if (m2 != null) 140 m = m2; 141 } 142 return m; 143 } 144 145 private String[] getPrefixes(Class<?> c) { 146 String[] prefixes = prefixCache.get(c); 147 if (prefixes == null) { 148 Set<String> ps = new HashSet<>(); 149 for (ClassInfo c2 : getClassInfo(c).getAllParents()) { 150 ConfigurableContext cc = c2.getAnnotation(ConfigurableContext.class); 151 if (cc != null) { 152 if (cc.nocache()) { 153 prefixes = new String[0]; 154 break; 155 } 156 if (cc.prefixes().length == 0) 157 ps.add(c2.getSimpleName()); 158 else 159 ps.addAll(Arrays.asList(cc.prefixes())); 160 } 161 } 162 prefixes = ps.toArray(new String[ps.size()]); 163 String[] p2 = prefixCache.putIfAbsent(c, prefixes); 164 if (p2 != null) 165 prefixes = p2; 166 } 167 return prefixes.length == 0 ? null : prefixes; 168 } 169 170 private <T> T newInstance(Class<T> cc, PropertyStore ps) throws Exception { 171 return (T)castOrCreate(Context.class, cc, true, ps); 172 } 173 174 private static class CacheEntry { 175 final PropertyStore ps; 176 final Context context; 177 178 CacheEntry(PropertyStore ps, Context context) { 179 this.ps = ps; 180 this.context = context; 181 } 182 } 183}