org.codehaus.groovy.grails.web.pages.discovery.CachingGrailsConventionGroovyPageLocator.java Source code

Java tutorial

Introduction

Here is the source code for org.codehaus.groovy.grails.web.pages.discovery.CachingGrailsConventionGroovyPageLocator.java

Source

/*
 * Copyright 2011 SpringSource
 *
 * 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.
 */
package org.codehaus.groovy.grails.web.pages.discovery;

import java.security.PrivilegedAction;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.codehaus.groovy.grails.web.pages.GroovyPageBinding;
import org.codehaus.groovy.grails.web.util.CacheEntry;
import org.springframework.core.io.ByteArrayResource;

/**
 * Extends GrailsConventionGroovyPageLocator adding caching of the located GrailsPageScriptSource.
 *
 * @author Graeme Rocher
 * @since 2.0
 */
public class CachingGrailsConventionGroovyPageLocator extends GrailsConventionGroovyPageLocator {

    private static final GroovyPageResourceScriptSource NULL_SCRIPT = new GroovyPageResourceScriptSource("/null",
            new ByteArrayResource("".getBytes()));
    private Map<GroovyPageLocatorCacheKey, CacheEntry<GroovyPageScriptSource>> uriResolveCache = new ConcurrentHashMap<GroovyPageLocatorCacheKey, CacheEntry<GroovyPageScriptSource>>();
    private long cacheTimeout = -1;

    @Override
    public GroovyPageScriptSource findPageInBinding(final String uri, final GroovyPageBinding binding) {
        if (uri == null)
            return null;

        PrivilegedAction<GroovyPageScriptSource> updater = new PrivilegedAction<GroovyPageScriptSource>() {
            public GroovyPageScriptSource run() {
                GroovyPageScriptSource scriptSource = CachingGrailsConventionGroovyPageLocator.super.findPageInBinding(
                        uri, binding);
                if (scriptSource == null) {
                    scriptSource = NULL_SCRIPT;
                }
                return scriptSource;
            }
        };

        return lookupCache(GroovyPageLocatorCacheKey.build(uri, null, binding), updater);
    }

    @Override
    public GroovyPageScriptSource findPageInBinding(final String pluginName, final String uri,
            final GroovyPageBinding binding) {
        if (uri == null || pluginName == null)
            return null;

        PrivilegedAction<GroovyPageScriptSource> updater = new PrivilegedAction<GroovyPageScriptSource>() {
            public GroovyPageScriptSource run() {
                GroovyPageScriptSource scriptSource = CachingGrailsConventionGroovyPageLocator.super.findPageInBinding(
                        pluginName, uri, binding);
                if (scriptSource == null) {
                    scriptSource = NULL_SCRIPT;
                }
                return scriptSource;
            }
        };

        return lookupCache(GroovyPageLocatorCacheKey.build(uri, pluginName, binding), updater);
    }

    @Override
    public GroovyPageScriptSource findPage(final String uri) {
        if (uri == null)
            return null;

        PrivilegedAction<GroovyPageScriptSource> updater = new PrivilegedAction<GroovyPageScriptSource>() {
            public GroovyPageScriptSource run() {
                GroovyPageScriptSource scriptSource = CachingGrailsConventionGroovyPageLocator.super.findPage(uri);
                if (scriptSource == null) {
                    scriptSource = NULL_SCRIPT;
                }
                return scriptSource;
            }
        };

        return lookupCache(GroovyPageLocatorCacheKey.build(uri, null, null), updater);
    }

    protected GroovyPageScriptSource lookupCache(final GroovyPageLocatorCacheKey cacheKey,
            PrivilegedAction<GroovyPageScriptSource> updater) {
        GroovyPageScriptSource scriptSource = null;
        if (cacheTimeout == 0) {
            scriptSource = updater.run();
        } else {
            CacheEntry<GroovyPageScriptSource> entry = uriResolveCache.get(cacheKey);
            if (entry == null) {
                scriptSource = updater.run();
                uriResolveCache.put(cacheKey, new CustomCacheEntry<GroovyPageScriptSource>(scriptSource));
            } else {
                scriptSource = entry.getValue(cacheTimeout, updater);
            }
        }
        return scriptSource == NULL_SCRIPT ? null : scriptSource;
    }

    public long getCacheTimeout() {
        return cacheTimeout;
    }

    public void setCacheTimeout(long cacheTimeout) {
        this.cacheTimeout = cacheTimeout;
    }

    private static final class GroovyPageLocatorCacheKey {
        private final String uri;
        private final String pluginName;
        private final String contextPath;

        private GroovyPageLocatorCacheKey(String uri, String pluginName, String contextPath) {
            this.uri = uri;
            this.pluginName = pluginName;
            this.contextPath = contextPath;
        }

        public static final GroovyPageLocatorCacheKey build(final String uri, final String pluginName,
                final GroovyPageBinding binding) {
            String pluginNameInCacheKey = (pluginName == null)
                    ? (binding != null
                            ? (binding.getPagePlugin() != null ? binding.getPagePlugin().getName() : null)
                            : null)
                    : pluginName;
            return new GroovyPageLocatorCacheKey(uri, pluginNameInCacheKey,
                    binding != null ? binding.getPluginContextPath() : null);
        }

        @Override
        public final int hashCode() {
            return new HashCodeBuilder().append(contextPath).append(pluginName).append(uri).toHashCode();
        }

        @Override
        public final boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;

            GroovyPageLocatorCacheKey other = (GroovyPageLocatorCacheKey) obj;
            return new EqualsBuilder().append(other.contextPath, contextPath).append(other.pluginName, pluginName)
                    .append(other.uri, uri).isEquals();
        }
    }

    @Override
    public void removePrecompiledPage(GroovyPageCompiledScriptSource scriptSource) {
        super.removePrecompiledPage(scriptSource);
        // remove the entry from uriResolveCache
        for (Map.Entry<GroovyPageLocatorCacheKey, CacheEntry<GroovyPageScriptSource>> entry : new HashSet<Map.Entry<GroovyPageLocatorCacheKey, CacheEntry<GroovyPageScriptSource>>>(
                uriResolveCache.entrySet())) {
            GroovyPageScriptSource ss = entry.getValue().getValue();
            if (ss == scriptSource || (ss instanceof GroovyPageCompiledScriptSource
                    && scriptSource.getURI().equals(((GroovyPageCompiledScriptSource) ss).getURI()))) {
                uriResolveCache.remove(entry.getKey());
            }
        }
    }

    private static class CustomCacheEntry<T> extends CacheEntry<T> {
        public CustomCacheEntry(T value) {
            super(value);
        }

        @Override
        protected boolean shouldUpdate(long beforeLockingCreatedMillis) {
            // Never expire GroovyPageCompiledScriptSource entry in cache
            boolean compiledScriptSourceInstance = getValue() instanceof GroovyPageCompiledScriptSource;
            if (compiledScriptSourceInstance) {
                resetTimestamp();
            }
            return !compiledScriptSourceInstance && super.shouldUpdate(beforeLockingCreatedMillis);
        }
    }
}