/*
 * #%L
 * ACS AEM Commons Bundle
 * %%
 * Copyright (C) 2014 Adobe
 * %%
 * Licensed 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
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 * #L%
 */

package com.adobe.acs.commons.errorpagehandler.cache.impl;

import com.adobe.acs.commons.util.ResourceDataUtil;
import com.adobe.granite.jmx.annotation.AnnotatedStandardMBean;

import org.apache.commons.lang.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.management.NotCompliantMBeanException;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import javax.management.openmbean.TabularData;
import javax.management.openmbean.TabularDataSupport;
import javax.management.openmbean.TabularType;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public final class ErrorPageCacheImpl extends AnnotatedStandardMBean implements ErrorPageCache, ErrorPageCacheMBean {
    private static final Logger log = LoggerFactory.getLogger(ErrorPageCacheImpl.class);

    private static final int KB_IN_BYTES = 1000;

    private final ConcurrentMap<String, CacheEntry> cache = new ConcurrentHashMap<String, CacheEntry>();

    private final int ttl;

    private final boolean serveAuthenticatedFromCache;


    public ErrorPageCacheImpl(int ttl, boolean serveAuthenticatedFromCache) throws NotCompliantMBeanException {
        super(ErrorPageCacheMBean.class);
        this.ttl = ttl;
        this.serveAuthenticatedFromCache = serveAuthenticatedFromCache;

        log.info("Starting ACS AEM Commons Error Page Handler Cache");
        log.info(" > TTL (in seconds): {}", ttl);
        log.info(" > Serve authenticated requests from cache: {}", serveAuthenticatedFromCache);
    }


    @Override
    public String get(final String path,
                            final SlingHttpServletRequest request,
                            final SlingHttpServletResponse response) {


        if (!serveAuthenticatedFromCache && !isAnonymousRequest(request)) {
            // For authenticated requests, don't return from cache
            return ResourceDataUtil.getIncludeAsString(path, request, response);
        }

        final long start = System.currentTimeMillis();
        CacheEntry cacheEntry = cache.get(path);
        final boolean newEntry = cacheEntry == null;

        if (newEntry || cacheEntry.isExpired(new Date())) {

            // Cache Miss
            final String data = ResourceDataUtil.getIncludeAsString(path, request, response);

            if (newEntry) {
                cacheEntry = new CacheEntry();
            }

            cacheEntry.setData(data);
            cacheEntry.setExpiresIn(ttl);
            cacheEntry.incrementMisses();

            if (newEntry) {
                // Add entry to cache
                cache.put(path, cacheEntry);
            }

            if (log.isInfoEnabled()) {
                final long time = System.currentTimeMillis() - start;
                log.info("Served cache MISS for [ {} ] in [ {} ] ms", path, time);
            }

            return data;
        } else {
            // Cache Hit

            final String data = cacheEntry.getData();

            cacheEntry.incrementHits();
            cache.put(path, cacheEntry);

            if (log.isInfoEnabled()) {
                final long time = System.currentTimeMillis() - start;
                log.info("Served cache HIT for [ {} ] in [ {} ] ms", path, time);
            }

            return data;
        }
    }

    private boolean isAnonymousRequest(final SlingHttpServletRequest request) {
        return (request.getAuthType() == null || request.getRemoteUser() == null);
    }

    /* MBean Attributes */

    @Override
    public final int getTtlInSeconds() {
        return ttl;
    }

    @Override
    public final int getTotalHits() {
        int hits = 0;

        for (final CacheEntry entry : this.cache.values()) {
            hits = hits + entry.getHits();
        }

        return hits;
    }

    @Override
    public final int getCacheEntriesCount() {
        if (this.cache == null) {
            return 0;
        } else {
            return this.cache.size();
        }
    }

    @Override
    public final int getTotalMisses() {
        int misses = 0;

        for (final Map.Entry<String, CacheEntry> entry : this.cache.entrySet()) {
            misses = misses + entry.getValue().getMisses();
        }

        return misses;
    }

    @Override
    public final int getTotalCacheRequests() {
        return this.getTotalHits() + this.getTotalMisses();
    }

    @Override
    public final long getCacheSizeInKB() {
        long bytes = 0;

        for (final CacheEntry entry : this.cache.values()) {
            bytes = bytes + entry.getBytes();
        }

        return bytes / KB_IN_BYTES;
    }


    public final TabularData getCacheEntries() throws OpenDataException {

        final CompositeType cacheEntryType = new CompositeType(
                "cacheEntry",
                "Cache Entry",
                new String[]{"errorPage", "hit", "miss", "hitRate", "missRate", "sizeInKB" },
                new String[]{"Error Page", "Hit", "Miss", "Hit Rate", "Miss Rate", "Size in KB" },
                new OpenType[]{SimpleType.STRING, SimpleType.INTEGER, SimpleType.INTEGER, SimpleType.FLOAT,
                        SimpleType.FLOAT, SimpleType.INTEGER }
        );

        final TabularDataSupport tabularData = new TabularDataSupport(
                new TabularType("cacheEntries",
                        "Cache Entries",
                        cacheEntryType,
                        new String[]{"errorPage" })
        );

        for (final Map.Entry<String, CacheEntry> entry : this.cache.entrySet()) {
            final CacheEntry cacheEntry = entry.getValue();

            final Map<String, Object> data = new HashMap<String, Object>();
            data.put("errorPage", entry.getKey());
            data.put("hit", cacheEntry.getHits());
            data.put("miss", cacheEntry.getMisses());
            data.put("hitRate", cacheEntry.getHitRate());
            data.put("missRate", cacheEntry.getMissRate());
            data.put("sizeInKB", cacheEntry.getBytes() / KB_IN_BYTES);

            tabularData.put(new CompositeDataSupport(cacheEntryType, data));
        }

        return tabularData;
    }

    /* MBean Operations */

    @Override
    public final void clearCache() {
        this.cache.clear();
    }

    @Override
    public final String getCacheData(final String errorPage) {
        final CacheEntry cacheEntry = this.cache.get(StringUtils.trim(errorPage));
        if (cacheEntry == null) {
            return "";
        }

        return cacheEntry.getData();
    }
}

@@ -3683,36 +3683,37 @@
       if (log.is
-Info
+Debug
 Enabled()) %7B%0A   
@@ -3791,36 +3791,37 @@
             log.
-info
+debug
 (%22Served cache M
@@ -4109,12 +4109,13 @@
 g.is
-Info
+Debug
 Enab
@@ -4213,20 +4213,21 @@
     log.
-info
+debug
 (%22Served

Change logging from info to debug