/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the License at the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package org.apereo.portal.utils.cache; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import net.sf.ehcache.Ehcache; import net.sf.ehcache.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; /** * This service supports custom {@link net.sf.ehcache.event.CacheEventListener} objects that * recognize when "something bad" is happening with a uPortal cache. Those listeners quickly contact * this service with the details. Periodically this service produces a report on bad things that are * occurring, if any, and writes it to the log. * */ @Service("cacheHealthReporterService") public class CacheHealthReporterService { protected final Logger logger = LoggerFactory.getLogger(getClass()); private enum Reports { CACHE_SIZE_NOT_LARGE_ENOUGH { @Override protected void doReportInternal(Logger logger, List<ReportTuple> list) { if (list.size() != 0) { final StringBuilder report = new StringBuilder(); final Map<String, Integer> counts = new HashMap<>(); for (ReportTuple tuple : list) { final String cacheName = tuple.getCache().getName(); Integer newCount = 1; // default Integer oldCount = counts.get(cacheName); if (oldCount != null) { newCount = ++oldCount; } counts.put(cacheName, newCount); } report.append( "The following cache(s) have insufficient maxElementsInMemory; " + "there must be room in the cache to hold every object of " + "the corresponding type in the portal:"); for (Map.Entry<String, Integer> y : counts.entrySet()) { report.append("\n\t- ") .append(y.getKey()) .append(" (") .append(y.getValue()) .append( " elements evicted due to insufficient size since last report)"); } logger.warn(report.toString()); } } }; private final List<ReportTuple> entries = Collections.synchronizedList(new ArrayList<ReportTuple>()); public final void add(ReportTuple tuple) { entries.add(tuple); } public final void writeReport(Logger logger) { // Copy & Reset the entries list final List<ReportTuple> list = new ArrayList<>(entries); entries.clear(); doReportInternal(logger, list); } protected abstract void doReportInternal(Logger logger, List<ReportTuple> list); } public void generateReports() { for (Reports report : Reports.values()) { report.writeReport(logger); } } @Async public void reportCacheSizeNotLargeEnough(Ehcache cache, Element element) { final ReportTuple tuple = new ReportTuple(cache, element); Reports.CACHE_SIZE_NOT_LARGE_ENOUGH.add(tuple); } /* * Nested Types */ private static final class ReportTuple { private final Ehcache cache; private final Element element; public ReportTuple(Ehcache cache, Element element) { this.cache = cache; this.element = element; } public Ehcache getCache() { return cache; } public Element getElement() { return element; } } }