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.collections.*; 021import org.apache.juneau.reflect.*; 022 023/** 024 * Stores a cache of {@link Context} instances mapped by the property stores used to create them. 025 * 026 * <p> 027 * The purpose of this class is to reuse instances of bean contexts, serializers, and parsers when they're being 028 * re-created with previously-used property stores. 029 * 030 * <p> 031 * Since serializers and parsers are immutable and thread-safe, we reuse them whenever possible. 032 */ 033@SuppressWarnings("unchecked") 034public class ContextCache { 035 036 /** 037 * Reusable cache instance. 038 */ 039 public static final ContextCache INSTANCE = new ContextCache(); 040 041 private final static boolean USE_DEEP_MATCHING = Boolean.getBoolean("ContextCache.useDeepMatching"); 042 043 private final ConcurrentHashMap<Class<?>,ConcurrentHashMap<Integer,CacheEntry>> contextCache = new ConcurrentHashMap<>(); 044 private final ConcurrentHashMap<Class<?>,String[]> prefixCache = new ConcurrentHashMap<>(); 045 046 // When enabled, this will spit out cache-hit metrics to the console on shutdown. 047 private static final boolean TRACK_CACHE_HITS = Boolean.getBoolean("juneau.trackCacheHits"); 048 static final Map<String,CacheHit> CACHE_HITS = new ConcurrentHashMap<>(); 049 static { 050 if (TRACK_CACHE_HITS) { 051 Runtime.getRuntime().addShutdownHook( 052 new Thread() { 053 @Override 054 public void run() { 055 int creates=0, cached=0; 056 System.out.println("Cache Hits: [CacheObject] = [numCreated,numCached,cacheHitPercentage]"); 057 for (Map.Entry<String,CacheHit> e : CACHE_HITS.entrySet()) { 058 CacheHit ch = e.getValue(); 059 System.out.println("["+e.getKey()+"] = ["+ch.creates+","+ch.cached+","+((ch.cached*100)/(ch.creates+ch.cached))+"%]"); 060 creates += ch.creates; 061 cached += ch.cached; 062 } 063 if (creates + cached > 0) 064 System.out.println("[total] = ["+creates+","+cached+","+((cached*100)/(creates+cached))+"%]"); 065 } 066 } 067 ); 068 } 069 } 070 071 static void logCache(Class<?> contextClass, boolean wasCached) { 072 if (TRACK_CACHE_HITS) { 073 synchronized(ContextCache.class) { 074 String c = contextClass.getSimpleName(); 075 CacheHit ch = CACHE_HITS.get(c); 076 if (ch == null) 077 ch = new CacheHit(); 078 if (wasCached) 079 ch.cached++; 080 else 081 ch.creates++; 082 ch = CACHE_HITS.put(c, ch); 083 } 084 } 085 } 086 087 static class CacheHit { 088 public int creates, cached; 089 } 090 091 ContextCache() {} 092 093 /** 094 * Creates a new instance of the specified context-based class, or an existing instance if one with the same 095 * property store was already created. 096 * 097 * @param c The instance of the class to create. 098 * @param ps The property store to use to create the class. 099 * @return The 100 */ 101 public <T extends Context> T create(Class<T> c, PropertyStore ps) { 102 String[] prefixes = getPrefixes(c); 103 104 if (prefixes == null) 105 return instantiate(c, ps); 106 107 ConcurrentHashMap<Integer,CacheEntry> m = getContextCache(c); 108 109 Integer hashCode = ps.hashCode(prefixes); 110 CacheEntry ce = m.get(hashCode); 111 112 if (ce != null && USE_DEEP_MATCHING && ! ce.ps.equals(ps)) 113 throw new ContextRuntimeException("Property store hashcode mismatch!", new Object[0]); 114 115 logCache(c, ce != null); 116 117 if (ce == null) { 118 ce = new CacheEntry(ps, instantiate(c, ps)); 119 m.putIfAbsent(hashCode, ce); 120 } 121 122 return (T)ce.context; 123 } 124 125 private <T extends Context> T instantiate(Class<T> c, PropertyStore ps) { 126 try { 127 return newInstance(c, ps); 128 } catch (ContextRuntimeException e) { 129 throw e; 130 } catch (Exception e) { 131 throw new ContextRuntimeException(e, "Could not create instance of class ''{0}''", c); 132 } 133 } 134 135 private ConcurrentHashMap<Integer,CacheEntry> getContextCache(Class<?> c) { 136 ConcurrentHashMap<Integer,CacheEntry> m = contextCache.get(c); 137 if (m == null) { 138 m = new ConcurrentHashMap<>(); 139 ConcurrentHashMap<Integer,CacheEntry> m2 = contextCache.putIfAbsent(c, m); 140 if (m2 != null) 141 m = m2; 142 } 143 return m; 144 } 145 146 private String[] getPrefixes(Class<?> c) { 147 String[] prefixes = prefixCache.get(c); 148 if (prefixes == null) { 149 ASet<String> ps = ASet.of(); 150 for (ClassInfo c2 : ClassInfo.of(c).getAllParentsChildFirst()) { 151 ConfigurableContext cc = c2.getLastAnnotation(ConfigurableContext.class); 152 if (cc != null) { 153 if (cc.nocache()) { 154 prefixes = new String[0]; 155 break; 156 } 157 if (cc.prefixes().length == 0) 158 ps.add(c2.getSimpleName()); 159 else 160 ps.a(cc.prefixes()); 161 } 162 } 163 prefixes = ps.toArray(new String[ps.size()]); 164 String[] p2 = prefixCache.putIfAbsent(c, prefixes); 165 if (p2 != null) 166 prefixes = p2; 167 } 168 return prefixes.length == 0 ? null : prefixes; 169 } 170 171 private <T> T newInstance(Class<T> cc, PropertyStore ps) throws Exception { 172 return (T)castOrCreate(Context.class, cc, true, ps); 173 } 174 175 private static class CacheEntry { 176 final PropertyStore ps; 177 final Context context; 178 179 CacheEntry(PropertyStore ps, Context context) { 180 this.ps = ps; 181 this.context = context; 182 } 183 } 184}