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