Skip to content

Commit

Permalink
changed to load caches from *CacheConfig.groovy classes (using an art…
Browse files Browse the repository at this point in the history
…efact handler) in addition to Config.groovy
  • Loading branch information
Burt Beckwith committed Apr 30, 2012
1 parent 0d6d250 commit d04b711
Show file tree
Hide file tree
Showing 12 changed files with 359 additions and 29 deletions.
3 changes: 2 additions & 1 deletion .classpath
Expand Up @@ -8,8 +8,9 @@
<classpathentry kind="src" path="grails-app/services"/>
<classpathentry kind="src" path="grails-app/taglib"/>
<classpathentry kind="src" path="grails-app/views"/>
<classpathentry kind="src" path="test/unit"/>
<classpathentry kind="src" path="test/functional"/>
<classpathentry kind="src" path="test/integration"/>
<classpathentry kind="src" path="test/unit"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry exported="true" kind="con" path="GROOVY_DSL_SUPPORT"/>
<classpathentry kind="con" path="com.springsource.sts.grails.core.CLASSPATH_CONTAINER"/>
Expand Down
41 changes: 27 additions & 14 deletions CacheGrailsPlugin.groovy
Expand Up @@ -12,7 +12,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import grails.plugin.cache.ConfigBuilder
import grails.plugin.cache.CacheConfigArtefactHandler
import grails.plugin.cache.ConfigLoader
import grails.plugin.cache.GrailsConcurrentMapCacheManager
import grails.plugin.cache.web.ProxyAwareMixedGrailsControllerHelper
import grails.plugin.cache.web.filter.DefaultWebKeyGenerator
Expand All @@ -24,6 +25,7 @@ import org.codehaus.groovy.grails.commons.GrailsApplication
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean
import org.springframework.context.ApplicationContext
import org.springframework.core.Ordered
import org.springframework.web.filter.DelegatingFilterProxy

Expand All @@ -35,6 +37,11 @@ class CacheGrailsPlugin {
def grailsVersion = '2.0 > *'
def observe = ['controllers']
def loadAfter = ['controllers']
def artefacts = [CacheConfigArtefactHandler]
def watchedResources = [
'file:./grails-app/conf/**/*CacheConfig.groovy',
'file:./plugins/*/grails-app/conf/**/*CacheConfig.groovy'
]

def title = 'Cache Plugin'
def author = 'Jeff Brown'
Expand All @@ -50,7 +57,7 @@ class CacheGrailsPlugin {

def pluginExcludes = [
'**/com/demo/**',
'grails-app/i18n/**',
'grails-app/conf/TestCacheConfig.groovy',
'grails-app/views/**',
'web-app/**'
]
Expand Down Expand Up @@ -109,19 +116,9 @@ class CacheGrailsPlugin {
mode: 'proxy', order: order,
'proxy-target-class': proxyTargetClass)

// TODO how do extension plugins configure these?
def configuredCacheNames = ['grailsBlocksCache', 'grailsTemplatesCache']
if (cacheConfig.config instanceof Closure) {
ConfigBuilder builder = new ConfigBuilder()
builder.parse cacheConfig.config
configuredCacheNames.addAll builder.cacheNames
}
grailsCacheManager(GrailsConcurrentMapCacheManager)

grailsCacheManager(GrailsConcurrentMapCacheManager) {
// TODO this locks the manager and doesn't allow new caches;
// could call getCache() for each name in doWithApplicationContext instead
cacheNames = configuredCacheNames
}
grailsCacheConfigLoader(ConfigLoader)

webCacheKeyGenerator(DefaultWebKeyGenerator)

Expand All @@ -140,6 +137,10 @@ class CacheGrailsPlugin {
}
}

def doWithApplicationContext = { ctx ->
reloadCaches ctx
}

def onChange = { event ->

if (!isEnabled(event.application)) {
Expand All @@ -157,6 +158,18 @@ class CacheGrailsPlugin {
if (event.application.isServiceClass(event.source)) {
// TODO reload CacheOperation config based on updated annotations
}

if (event.application.isCacheConfigClass(event.source)) {
reloadCaches event.ctx
}
}

def onConfigChange = { event ->
reloadCaches event.ctx
}

private void reloadCaches(ctx) {
ctx.grailsCacheConfigLoader.reload ctx
}

private boolean isEnabled(GrailsApplication application) {
Expand Down
6 changes: 1 addition & 5 deletions grails-app/conf/Config.groovy
Expand Up @@ -16,10 +16,6 @@ log4j = {
// for tests
grails.cache.config = {
cache {
name 'basic'
eternal false
overflowToDisk true
maxElementsInMemory 10000
maxElementsOnDisk 10000000
name 'fromConfigGroovy'
}
}
8 changes: 8 additions & 0 deletions grails-app/conf/DefaultCacheConfig.groovy
@@ -0,0 +1,8 @@
cacheConfig = {
cache {
name 'grailsBlocksCache'
}
cache {
name 'grailsTemplatesCache'
}
}
9 changes: 9 additions & 0 deletions grails-app/conf/TestCacheConfig.groovy
@@ -0,0 +1,9 @@
cacheConfig = {
cache {
name 'basic'
eternal false
overflowToDisk true
maxElementsInMemory 10000
maxElementsOnDisk 10000000
}
}
5 changes: 5 additions & 0 deletions src/groovy/grails/plugin/cache/ConfigBuilder.groovy
Expand Up @@ -47,6 +47,11 @@ class ConfigBuilder extends BuilderSupport {
resolveCaches()
}

void parse(o) {
// if there's no explicit method, the missing method logic kicks in and fails poorly
throw new IllegalArgumentException('parse must be called with a Closure argument')
}

@Override
protected createNode(name) {
if (_unrecognizedElementDepth) {
Expand Down
117 changes: 117 additions & 0 deletions src/groovy/grails/plugin/cache/ConfigLoader.groovy
@@ -0,0 +1,117 @@
/* Copyright 2012 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 grails.plugin.cache

import org.codehaus.groovy.grails.commons.GrailsApplication
import org.springframework.context.ApplicationContext

import grails.plugin.cache.CacheConfigArtefactHandler.CacheConfigGrailsClass
import grails.util.Environment

/**
* @author Burt Beckwith
*/
class ConfigLoader {

static final int DEFAULT_ORDER = 1000

void reload(ApplicationContext ctx) {
def application = ctx.grailsApplication
List<ConfigObject> configs = loadOrderedConfigs(application)
reload configs, ctx
}

void reload(List<ConfigObject> configs, ApplicationContext ctx) {

// order doesn't matter in this impl, but in general process in reverse order so
// lower order values have higher priority and can override previous settings
def configuredCacheNames = [] as LinkedHashSet
for (ListIterator<ConfigObject> iter = configs.listIterator(configs.size()); iter.hasPrevious(); ) {
ConfigObject co = iter.previous()
ConfigBuilder builder = new ConfigBuilder()
def config = co.cacheConfig ?: co.config
if (config instanceof Closure) {
builder.parse config
}
configuredCacheNames.addAll builder.cacheNames
}

GrailsCacheManager cacheManager = ctx.grailsCacheManager

for (String name in cacheManager.cacheNames) {
if (!configuredCacheNames.contains(name)) {
cacheManager.destroyCache name
}
}

for (String cacheName in configuredCacheNames) {
cacheManager.getCache cacheName
}
}

List<ConfigObject> loadOrderedConfigs(GrailsApplication application) {
ConfigSlurper slurper = new ConfigSlurper(Environment.current.name)

List<ConfigObject> configs = []
def cacheConfig
for (configClass in application.cacheConfigClasses) {
def config = slurper.parse(configClass.clazz)
cacheConfig = config.cacheConfig
if ((cacheConfig instanceof Closure) && processConfig(config, configClass)) {
configs << config
}
}

cacheConfig = application.config.grails.cache
if ((cacheConfig.config instanceof Closure) && processConfig(cacheConfig, null)) {
configs << cacheConfig
}

sortConfigs configs

configs
}

protected boolean processConfig(ConfigObject config, CacheConfigGrailsClass configClass) {
def cacheConfig
String sourceClassName

if (configClass == null) {
cacheConfig = config.config
sourceClassName = 'Config'
}
else {
cacheConfig = config.cacheConfig
sourceClassName = configClass.clazz.name
}

if (cacheConfig instanceof Closure) {
def order = config.order
if (!(order instanceof Number)) {
config.order = DEFAULT_ORDER
}
config._sourceClassName = sourceClassName
return true
}

false
}

protected void sortConfigs(List<Closure> configs) {
configs.sort { c1, c2 ->
c1.order == c2.order ? c1._sourceClassName <=> c2._sourceClassName : c1.order <=> c2.order
}
}
}
83 changes: 83 additions & 0 deletions src/java/grails/plugin/cache/CacheConfigArtefactHandler.java
@@ -0,0 +1,83 @@
/* Copyright 2012 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 grails.plugin.cache;

import groovy.lang.GroovySystem;
import groovy.lang.MetaClass;

import org.codehaus.groovy.grails.commons.AbstractInjectableGrailsClass;
import org.codehaus.groovy.grails.commons.ArtefactHandlerAdapter;
import org.codehaus.groovy.grails.commons.InjectableGrailsClass;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;

/**
* Artefact handler for CacheConfig classes.
*
* @author Burt Beckwith
*/
public class CacheConfigArtefactHandler extends ArtefactHandlerAdapter {

/** The artefact type. */
public static final String TYPE = "CacheConfig";

/**
* Default constructor.
*/
public CacheConfigArtefactHandler() {
super(TYPE, CacheConfigGrailsClass.class, DefaultCacheConfigGrailsClass.class, TYPE);
}

/**
* GrailsClass interface for CacheConfig definitions.
*/
public static interface CacheConfigGrailsClass extends InjectableGrailsClass {
// no methods
}

/**
* Default implementation of <code>CacheConfigGrailsClass</code>.
*/
public static class DefaultCacheConfigGrailsClass extends AbstractInjectableGrailsClass
implements CacheConfigGrailsClass {

/**
* Default constructor.
* @param wrappedClass
*/
public DefaultCacheConfigGrailsClass(Class<?> wrappedClass) {
super(wrappedClass, CacheConfigArtefactHandler.TYPE);
}

@Override
public MetaClass getMetaClass() {
// Workaround for http://jira.codehaus.org/browse/GRAILS-4542
return GroovySystem.getMetaClassRegistry().getMetaClass(DefaultCacheConfigGrailsClass.class);
}

@Override
public Object newInstance() {
Object instance = super.newInstance();
autowireBeanProperties(instance);
return instance;
}

protected void autowireBeanProperties(Object instance) {
ConfigurableApplicationContext ctx = (ConfigurableApplicationContext)grailsApplication.getMainContext();
ctx.getBeanFactory().autowireBeanProperties(instance,
AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, false);
}
}
}
27 changes: 27 additions & 0 deletions src/java/grails/plugin/cache/GrailsCacheManager.java
@@ -0,0 +1,27 @@
/* Copyright 2012 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 grails.plugin.cache;

import org.springframework.cache.CacheManager;

/**
* @author Burt Beckwith
*/
public interface GrailsCacheManager extends CacheManager {

boolean cacheExists(String name);

boolean destroyCache(String name);
}

0 comments on commit d04b711

Please sign in to comment.