001package org.cache2k.impl;
002
003/*
004 * #%L
005 * cache2k core package
006 * %%
007 * Copyright (C) 2000 - 2015 headissue GmbH, Munich
008 * %%
009 * This program is free software: you can redistribute it and/or modify
010 * it under the terms of the GNU General Public License as
011 * published by the Free Software Foundation, either version 3 of the 
012 * License, or (at your option) any later version.
013 * 
014 * This program is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 * 
019 * You should have received a copy of the GNU General Public 
020 * License along with this program.  If not, see
021 * <http://www.gnu.org/licenses/gpl-3.0.html>.
022 * #L%
023 */
024
025import org.cache2k.Cache;
026import org.cache2k.CacheException;
027import org.cache2k.CacheManager;
028import org.cache2k.impl.threading.Futures;
029import org.cache2k.impl.threading.GlobalPooledExecutor;
030import org.cache2k.impl.util.Cache2kVersion;
031import org.cache2k.impl.util.Log;
032
033import java.io.IOException;
034import java.io.InputStream;
035import java.net.URL;
036import java.util.ArrayList;
037import java.util.HashMap;
038import java.util.HashSet;
039import java.util.Iterator;
040import java.util.List;
041import java.util.Map;
042import java.util.ServiceLoader;
043import java.util.Set;
044import java.util.concurrent.ArrayBlockingQueue;
045import java.util.concurrent.ExecutionException;
046import java.util.concurrent.ExecutorService;
047import java.util.concurrent.Future;
048import java.util.concurrent.ThreadFactory;
049import java.util.concurrent.ThreadPoolExecutor;
050import java.util.concurrent.TimeUnit;
051import java.util.concurrent.atomic.AtomicInteger;
052import java.util.jar.Manifest;
053
054/**
055 * @author Jens Wilke; created: 2013-07-01
056 */
057public class CacheManagerImpl extends CacheManager {
058  
059  static List<CacheLifeCycleListener> cacheLifeCycleListeners = new ArrayList<CacheLifeCycleListener>();
060  static List<CacheManagerLifeCycleListener> cacheManagerLifeCycleListeners = new ArrayList<CacheManagerLifeCycleListener>();
061
062  static {
063    for (CacheLifeCycleListener l : ServiceLoader.load(CacheLifeCycleListener.class)) {
064      cacheLifeCycleListeners.add(l);
065    }
066    for (CacheManagerLifeCycleListener l : ServiceLoader.load(CacheManagerLifeCycleListener.class)) {
067      cacheManagerLifeCycleListeners.add(l);
068    }
069  }
070
071  private Log log;
072  private String name;
073  private Map<String, BaseCache> cacheNames = new HashMap<String, BaseCache>();
074  private Set<Cache> caches = new HashSet<Cache>();
075  private int disambiguationCounter = 1;
076  private GlobalPooledExecutor threadPool;
077  private AtomicInteger evictionThreadCount = new AtomicInteger();
078  private ExecutorService evictionExecutor;
079  private String version;
080  private String buildNumber;
081
082  public CacheManagerImpl() {
083    this(getDefaultName());
084  }
085
086  public CacheManagerImpl(String _name) {
087    name = _name;
088    log = Log.getLog(CacheManager.class.getName() + '.' + name);
089    String _buildNumber = Cache2kVersion.getBuildNumber();
090    String _version = Cache2kVersion.getVersion();
091    StringBuilder sb = new StringBuilder();
092    sb.append("org.cache2k manager starting. ");
093    sb.append("name="); sb.append(name);
094    sb.append(", version="); sb.append(_version);
095    sb.append(", build="); sb.append(_buildNumber);
096    log.info(sb.toString());
097    for (CacheManagerLifeCycleListener lc : cacheManagerLifeCycleListeners) {
098      lc.managerCreated(this);
099    }
100  }
101
102  private void sendCreatedEvent(Cache c) {
103    for (CacheLifeCycleListener e : cacheLifeCycleListeners) {
104      e.cacheCreated(this, c);
105    }
106  }
107
108  private void sendDestroyedEvent(Cache c) {
109    for (CacheLifeCycleListener e : cacheLifeCycleListeners) {
110      e.cacheDestroyed(this, c);
111    }
112  }
113
114  /**
115   * Don't accept a cache name with too weird characters. Rather then escaping the
116   * name, so we can use it for JMX, it is better to just reject it.
117   */
118  private void checkName(String s) {
119    for (char c : s.toCharArray()) {
120      if (c == '.' ||
121          c == '-' ||
122          c == '~') {
123        continue;
124      }
125      if (!Character.isJavaIdentifierPart(c)) {
126        throw new CacheUsageExcpetion(
127          "Cache name contains illegal chars: '" + c + "', name=\"" + s + "\"");
128      }
129    }
130  }
131
132  static class StackTrace extends Exception { }
133
134  /* called by builder */
135  public synchronized void newCache(Cache c) {
136    BaseCache bc = (BaseCache) c;
137    String _requestedName = c.getName();
138    String _name = _requestedName;
139    while (cacheNames.containsKey(_name)) {
140      _name = _requestedName + "~" + Integer.toString(disambiguationCounter++, 36);
141    }
142    if (!_requestedName.equals(_name)) {
143      log.warn("duplicate name, disambiguating: " + _requestedName + " -> " + _name, new StackTrace());
144      bc.setName(_name);
145    }
146    checkName(_name);
147
148    caches.add(c);
149    sendCreatedEvent(c);
150    bc.setCacheManager(this);
151    cacheNames.put(c.getName(), bc);
152  }
153
154  /* called by cache or CM */
155  public synchronized void cacheDestroyed(Cache c) {
156    cacheNames.remove(c.getName());
157    caches.remove(c);
158    sendDestroyedEvent(c);
159  }
160
161  @Override
162  public String getName() {
163    return name;
164  }
165
166  @Override
167  public synchronized Iterator<Cache> iterator() {
168    checkClosed();
169    return caches.iterator();
170  }
171
172  @Override
173  public Cache getCache(String name) {
174    return cacheNames.get(name);
175  }
176
177  @Override
178  public synchronized void clear() {
179    checkClosed();
180    for (Cache c : caches) {
181      c.clear();
182    }
183  }
184
185  @Override
186  public synchronized void destroy() {
187    close();
188  }
189
190  /**
191   * The shutdown takes place in two phases. First all caches are notified to
192   * cancel their scheduled timer jobs, after that the shutdown is done. Cancelling
193   * the timer jobs first is needed, because there may be cache stacking and
194   * a timer job of one cache may call an already closed cache.
195   *
196   * <p/>Rationale exception handling: Exceptions on shutdown just could silently
197   * ignored, because a shutdown is done any way. Exceptions could be happened
198   * long before in a parallel task, shutdown is the last point where this could
199   * be propagated to the application. Silently ignoring is bad anyway, because
200   * this will cause that serious problems keep undetected. The exception and
201   * error handling within the cache tries everything that exceptions will be
202   * routed through as early and directly as possible.
203   */
204  @Override
205  public synchronized void close() {
206    List<Throwable> _suppressedExceptions = new ArrayList<Throwable>();
207    if (caches != null) {
208      Futures.WaitForAllFuture<Void> _wait = new Futures.WaitForAllFuture<Void>();
209      for (Cache c : caches) {
210        if (c instanceof BaseCache) {
211          try {
212            Future<Void> f = ((BaseCache) c).cancelTimerJobs();
213            _wait.add(f);
214          } catch (Throwable t) {
215            _suppressedExceptions.add(t);
216          }
217        }
218      }
219      try {
220        _wait.get(3000, TimeUnit.MILLISECONDS);
221      } catch (Exception ex) {
222        _suppressedExceptions.add(ex);
223      }
224      if (!_wait.isDone()) {
225        for (Cache c : caches) {
226          if (c instanceof BaseCache) {
227            BaseCache bc = (BaseCache) c;
228            try {
229              Future<Void> f = bc.cancelTimerJobs();
230              if (!f.isDone()) {
231                bc.getLog().info("fetches ongoing, terminating anyway...");
232              }
233            } catch (Throwable t) {
234              _suppressedExceptions.add(t);
235            }
236          }
237        }
238      }
239      Set<Cache> _caches = new HashSet<Cache>();
240      _caches.addAll(caches);
241      for (Cache c : _caches) {
242        try {
243          c.destroy();
244        } catch (Throwable t) {
245          _suppressedExceptions.add(t);
246        }
247      }
248      try {
249        if (threadPool != null) {
250            threadPool.close();
251        }
252        for (CacheManagerLifeCycleListener lc : cacheManagerLifeCycleListeners) {
253          lc.managerDestroyed(this);
254        }
255        caches = null;
256      } catch (Throwable t) {
257        _suppressedExceptions.add(t);
258      }
259    }
260    eventuallyThrowException(_suppressedExceptions);
261  }
262
263  /**
264   * Throw exception if the list contains exceptions. The the thrown exception
265   * all exceptions get added as suppressed exceptions. The main cause of the
266   * exception will be the first detected error or the first detected
267   * exception.
268   *
269   * @throws org.cache2k.impl.CacheInternalError if list contains an error
270   * @throws org.cache2k.CacheException if list does not contain an error
271   */
272  private void eventuallyThrowException(List<Throwable> _suppressedExceptions) {
273    if (_suppressedExceptions.size() > 0) {
274      Throwable _error = null;
275      for (Throwable t : _suppressedExceptions) {
276        if (t instanceof Error) { _error = t; }
277        if (t instanceof ExecutionException &&
278          ((ExecutionException) t).getCause() instanceof Error) {
279          _error = t;
280        }
281      }
282      Throwable _throwNow;
283      String _text = "shutdown";
284      if (_suppressedExceptions.size() > 1) {
285        _text = " (" + _suppressedExceptions.size() + " exceptions)";
286      }
287      if (_error != null) {
288        _throwNow = new CacheInternalError(_text, _error);
289      } else {
290        _throwNow = new CacheException(_text, _suppressedExceptions.get(0));
291        _suppressedExceptions.remove(0);
292      }
293      for (Throwable t : _suppressedExceptions) {
294        if (t != _error) {
295          _throwNow.addSuppressed(t);
296        }
297      }
298      if (_error != null) {
299        throw (Error) _throwNow;
300      } else {
301        throw (RuntimeException) _throwNow;
302      }
303    }
304  }
305
306  @Override
307  public boolean isDestroyed() {
308    return caches == null;
309  }
310
311  private String getThreadNamePrefix() {
312    if (!DEFAULT_MANAGER_NAME.equals(name)) {
313      return "cache2k:" + name + ":";
314    }
315    return "cache2k-";
316  }
317
318  /**
319   * Lazy creation of thread pool, usable for all caches managed by the cache
320   * manager.
321   */
322  public synchronized GlobalPooledExecutor getThreadPool() {
323    if (threadPool == null) {
324      threadPool = new GlobalPooledExecutor(getThreadNamePrefix() + "pool-");
325    }
326    return threadPool;
327  }
328
329  public ExecutorService createEvictionExecutor() {
330    ThreadFactory _threadFactory = new ThreadFactory() {
331      @Override
332      public Thread newThread(Runnable r) {
333        String _name =
334            getThreadNamePrefix() + "eviction-" + evictionThreadCount.incrementAndGet();
335        Thread t = new Thread(new ExceptionWrapper(r), _name);
336        t.setDaemon(true);
337        return t;
338      }
339    };
340    return
341      new ThreadPoolExecutor(
342        0, Integer.MAX_VALUE, 17, TimeUnit.SECONDS,
343        new ArrayBlockingQueue<Runnable>(Runtime.getRuntime().availableProcessors() * 2), _threadFactory);
344  }
345
346  class ExceptionWrapper implements Runnable {
347
348    Runnable runnable;
349
350    ExceptionWrapper(Runnable runnable) {
351      this.runnable = runnable;
352    }
353
354    @Override
355    public void run() {
356      try {
357        runnable.run();
358      } catch (CacheClosedException ignore) {
359       } catch (Throwable t) {
360        log.warn("Exception in thread \"" + Thread.currentThread().getName() + "\"", t);
361      }
362    }
363
364  }
365
366  public ExecutorService getEvictionExecutor() {
367    if (evictionExecutor == null) {
368      evictionExecutor = createEvictionExecutor();
369    }
370    return evictionExecutor;
371  }
372
373  /**
374   * Only return thread pool if created before. For JMX bean access.
375   */
376  public GlobalPooledExecutor getThreadPoolEventually() {
377    return threadPool;
378  }
379
380  private void checkClosed() {
381    if (caches == null) {
382      throw new IllegalStateException("CacheManager already closed");
383    }
384  }
385
386  public String getVersion() {
387    return version;
388  }
389
390  public String getBuildNumber() {
391    return buildNumber;
392  }
393
394}