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}