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 = true; 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 for (Map.Entry<String,CacheHit> e : CACHE_HITS.entrySet()) { 054 CacheHit ch = e.getValue(); 055 System.out.println("["+e.getKey()+"] = ["+ch.creates+","+ch.cached+","+((ch.cached*100)/(ch.creates+ch.cached))+"%]"); 056 creates += ch.creates; 057 cached += ch.cached; 058 } 059 if (creates + cached > 0) 060 System.out.println("[total] = ["+creates+","+cached+","+((cached*100)/(creates+cached))+"%]"); 061 } 062 } 063 ); 064 } 065 } 066 067 static void logCache(Class<?> contextClass, boolean wasCached) { 068 if (TRACK_CACHE_HITS) { 069 synchronized(ContextCache.class) { 070 String c = contextClass.getSimpleName(); 071 CacheHit ch = CACHE_HITS.get(c); 072 if (ch == null) 073 ch = new CacheHit(); 074 if (wasCached) 075 ch.cached++; 076 else 077 ch.creates++; 078 ch = CACHE_HITS.put(c, ch); 079 } 080 } 081 } 082 083 static class CacheHit { 084 public int creates, cached; 085 } 086 087 ContextCache() {} 088 089 /** 090 * Creates a new instance of the specified context-based class, or an existing instance if one with the same 091 * property store was already created. 092 * 093 * @param c The instance of the class to create. 094 * @param ps The property store to use to create the class. 095 * @return The 096 */ 097 public <T extends Context> T create(Class<T> c, PropertyStore ps) { 098 ConcurrentHashMap<Integer,CacheEntry> m = getContextCache(c); 099 String[] prefixes = getPrefixes(c); 100 101 Integer hashCode = ps.hashCode(prefixes); 102 CacheEntry ce = m.get(hashCode); 103 104 if (ce != null && USE_DEEP_MATCHING && ! ce.ps.equals(ps)) 105 throw new ContextRuntimeException("Property store hashcode mismatch!"); 106 107 logCache(c, ce != null); 108 109 if (ce == null) { 110 try { 111 ce = new CacheEntry(ps, newInstance(c, ps)); 112 } catch (ContextRuntimeException e) { 113 throw e; 114 } catch (Exception e) { 115 throw new ContextRuntimeException(e, "Could not create instance of class ''{0}''", c); 116 } 117 m.putIfAbsent(hashCode, ce); 118 } 119 120 return (T)ce.context; 121 } 122 123 private ConcurrentHashMap<Integer,CacheEntry> getContextCache(Class<?> c) { 124 ConcurrentHashMap<Integer,CacheEntry> m = contextCache.get(c); 125 if (m == null) { 126 m = new ConcurrentHashMap<>(); 127 ConcurrentHashMap<Integer,CacheEntry> m2 = contextCache.putIfAbsent(c, m); 128 if (m2 != null) 129 m = m2; 130 } 131 return m; 132 } 133 134 private String[] getPrefixes(Class<?> c) { 135 String[] prefixes = prefixCache.get(c); 136 if (prefixes == null) { 137 Set<String> ps = new HashSet<>(); 138 for (Iterator<Class<?>> i = ClassUtils.getParentClasses(c, false, true); i.hasNext();) 139 ps.add(i.next().getSimpleName()); 140 prefixes = ps.toArray(new String[ps.size()]); 141 String[] p2 = prefixCache.putIfAbsent(c, prefixes); 142 if (p2 != null) 143 prefixes = p2; 144 } 145 return prefixes; 146 } 147 148 private <T> T newInstance(Class<T> cc, PropertyStore ps) throws Exception { 149 return (T)ClassUtils.newInstance(Context.class, cc, true, ps); 150 } 151 152 private static class CacheEntry { 153 final PropertyStore ps; 154 final Context context; 155 156 CacheEntry(PropertyStore ps, Context context) { 157 this.ps = ps; 158 this.context = context; 159 } 160 } 161}