Repository: zest-qi4j Updated Branches: refs/heads/develop 8f87df34c -> 4076eae35
Giving some love to the InvocationCache library, which was simply in a very bad shape. Project: http://git-wip-us.apache.org/repos/asf/zest-qi4j/repo Commit: http://git-wip-us.apache.org/repos/asf/zest-qi4j/commit/c24b54ec Tree: http://git-wip-us.apache.org/repos/asf/zest-qi4j/tree/c24b54ec Diff: http://git-wip-us.apache.org/repos/asf/zest-qi4j/diff/c24b54ec Branch: refs/heads/develop Commit: c24b54ecca37845c0665bb1575bdd3d09b97c3e2 Parents: 8f87df3 Author: Niclas Hedhman <[email protected]> Authored: Thu Jul 9 11:16:54 2015 +0300 Committer: Niclas Hedhman <[email protected]> Committed: Thu Jul 9 11:16:54 2015 +0300 ---------------------------------------------------------------------- .../src/docs/invocation-cache.txt | 69 +++++++++++++++++++- .../CacheInvocationResultSideEffect.java | 62 ------------------ .../InvalidateCacheOnSettersSideEffect.java | 53 --------------- .../invocationcache/InvocationCache.java | 3 + .../InvocationCacheAbstractComposite.java | 32 --------- .../invocationcache/InvocationCacheMixin.java | 60 ----------------- .../ReturnCachedValueConcern.java | 26 ++++---- .../ReturnCachedValueOnExceptionConcern.java | 33 +++++----- .../SimpleInvocationCacheMixin.java | 69 ++++++++++++++++++++ .../invocationcache/DocumentationSupport.java | 51 +++++++++++++++ 10 files changed, 215 insertions(+), 243 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/c24b54ec/libraries/invocation-cache/src/docs/invocation-cache.txt ---------------------------------------------------------------------- diff --git a/libraries/invocation-cache/src/docs/invocation-cache.txt b/libraries/invocation-cache/src/docs/invocation-cache.txt index da5e60f..40600b7 100644 --- a/libraries/invocation-cache/src/docs/invocation-cache.txt +++ b/libraries/invocation-cache/src/docs/invocation-cache.txt @@ -25,9 +25,72 @@ source=libraries/invocation-cache/dev-status.xml -------------- -The Invocation Cache Library provides constructs to easily cache composite methods invocations. -It has nothing to do with the <<core-spi-cache>>. +The Invocation Cache Library provides constructs to easily cache the return value of +method invocations on composites. -NOTE: This Library has no documentation yet. Learn how to contribute in <<community-docs>>. +NOTE: It has nothing to do with the <<core-spi-cache>>. include::../../build/docs/buildinfo/artifact.txt[] + + +By applying one of the <<def-concern,Concerns>> it is possible to cache the return values of method +calls. The concern will in turn delegate to the +InvocationCache+ that is expected to be a +<<def-private-mixin>> in the same composite. + +== +@Cached+ == +This annotation is used to mark the methods that should be considered for caching. Only if a +caching concern has been defined and that an +InvocationCache+ implementation mixin has been provided +will the caching actually take place. + +== +ReturnCachedValueConcern+ == +This generic mixin implementation will first look in the cache and see if the value is there, if so the value +is unconditionally returned to the caller. + +This concern skip its function if there is no +InvocationCache+ mixin declared on the composite or if the method +has a +void+ return type. + +== +ReturnCachedValueOnExceptionConcern+ == +This generic mixin implementation will first call the method, and if it fails with an Exception, it will try to +return a value from the cache. If no value is present in the cache (i.e. null is returned from the cache) then +the exception will be rethrown. + +This concern skip its function if there is no +InvocationCache+ mixin declared on the composite or if the method +has a +void+ return type. + +== Example == + +Let's say that we have some service that is very expensive to call. + +[snippet,java] +---- +source=libraries/invocationcache/src/test/java/org/qi4j/library/invocationcache/DocumentationSupport.java +tag=composite +---- + +And we know that the argument combinations into this method are relatively few, we can simply declare the ++SimpleInvocationCache+ mixin implementation to store the permutations and return them if already been +provided. + +[snippet,java] +---- +source=libraries/invocationcache/src/test/java/org/qi4j/library/invocationcache/DocumentationSupport.java +tag=assembly +---- + +It is important to realize that the +SimpleInvocationCache+ implementation never drops the cached values, +and it is not possible to instruct it to do so. So, in most cases it is required to implement the +InvocationCache+ +interface yourself, and choose a caching strategy that works for you. + +== Custom +InvocationCache+ implementation == +The interface to implement is very straight forward. It is important to realize that the implementation is a +<<def-private-mixin>> of the composite where the caching is applied, and not a separate service. So, if +the implementation is expecting to be part of an entity, it is possible to have + +[source,java] +---- +@This +private Identity myIdentity; +---- + +to get hold of the current entity's +Identity+. This approach makes the caching a lot simpler than if a separate +service would have been used instead, but still possible to delegate to such. \ No newline at end of file http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/c24b54ec/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/CacheInvocationResultSideEffect.java ---------------------------------------------------------------------- diff --git a/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/CacheInvocationResultSideEffect.java b/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/CacheInvocationResultSideEffect.java deleted file mode 100644 index fb3e73b..0000000 --- a/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/CacheInvocationResultSideEffect.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2007 Rickard Ãberg. - * - * 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.qi4j.library.invocationcache; - -import java.lang.reflect.Method; -import java.util.Arrays; -import org.qi4j.api.common.AppliesTo; -import org.qi4j.api.injection.scope.Invocation; -import org.qi4j.api.injection.scope.This; -import org.qi4j.api.sideeffect.GenericSideEffect; - -/** - * Cache result of @Cached method calls. - */ -@AppliesTo( Cached.class ) -public class CacheInvocationResultSideEffect - extends GenericSideEffect -{ - @This - private InvocationCache cache; - @Invocation - private Method method; - - @Override - public Object invoke( Object proxy, Method method, Object[] args ) - throws Throwable - { - // Get value - // if an exception is thrown, don't do anything - Object res = result.invoke( proxy, method, args ); - if( res == null ) - { - res = Void.TYPE; - } - String cacheName = method.getName(); - if( args != null ) - { - cacheName += Arrays.asList( args ); - } - Object oldResult = cache.cachedValue( cacheName ); - if( oldResult == null || !oldResult.equals( result ) ) - { - cache.setCachedValue( cacheName, result ); - } - return result; - } -} http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/c24b54ec/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/InvalidateCacheOnSettersSideEffect.java ---------------------------------------------------------------------- diff --git a/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/InvalidateCacheOnSettersSideEffect.java b/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/InvalidateCacheOnSettersSideEffect.java deleted file mode 100644 index f819a76..0000000 --- a/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/InvalidateCacheOnSettersSideEffect.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2007 Rickard Ãberg. - * - * 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.qi4j.library.invocationcache; - -import java.lang.reflect.Method; -import org.qi4j.api.common.AppliesTo; -import org.qi4j.api.common.AppliesToFilter; -import org.qi4j.api.injection.scope.This; -import org.qi4j.api.sideeffect.GenericSideEffect; - -/** - * Invalidate cache on setters. - */ -@AppliesTo( InvalidateCacheOnSettersSideEffect.AppliesTo.class ) -public class InvalidateCacheOnSettersSideEffect - extends GenericSideEffect -{ - public static class AppliesTo - implements AppliesToFilter - { - @Override - public boolean appliesTo( Method method, Class<?> mixin, Class<?> compositeType, Class<?> modifierClass ) - { - return !( method.getDeclaringClass().equals( InvocationCache.class ) - || method.getDeclaringClass().equals( InvocationCacheMixin.class ) ); - - } - } - - @This - private InvocationCache cache; - - @Override - protected void invoke( Method method, Object[] args ) - { - cache.clearCachedValues(); - } -} http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/c24b54ec/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/InvocationCache.java ---------------------------------------------------------------------- diff --git a/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/InvocationCache.java b/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/InvocationCache.java index 7d83355..4ab8e9c 100644 --- a/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/InvocationCache.java +++ b/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/InvocationCache.java @@ -17,9 +17,12 @@ */ package org.qi4j.library.invocationcache; +import org.qi4j.api.mixin.Mixins; + /** * Invocation Cache. */ +@Mixins( SimpleInvocationCacheMixin.class ) public interface InvocationCache { Object setCachedValue( String name, Object aResult ); http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/c24b54ec/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/InvocationCacheAbstractComposite.java ---------------------------------------------------------------------- diff --git a/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/InvocationCacheAbstractComposite.java b/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/InvocationCacheAbstractComposite.java deleted file mode 100644 index 183c490..0000000 --- a/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/InvocationCacheAbstractComposite.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2007 Rickard Ãberg. - * - * 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.qi4j.library.invocationcache; - -import org.qi4j.api.concern.Concerns; -import org.qi4j.api.mixin.Mixins; -import org.qi4j.api.sideeffect.SideEffects; - -/** - * JAVADOC - */ -@Mixins( InvocationCacheMixin.class ) -@Concerns( ReturnCachedValueOnExceptionConcern.class ) -@SideEffects( { CacheInvocationResultSideEffect.class, InvalidateCacheOnSettersSideEffect.class } ) -public interface InvocationCacheAbstractComposite -{ -} http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/c24b54ec/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/InvocationCacheMixin.java ---------------------------------------------------------------------- diff --git a/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/InvocationCacheMixin.java b/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/InvocationCacheMixin.java deleted file mode 100644 index b45a4a3..0000000 --- a/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/InvocationCacheMixin.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2007 Rickard Ãberg. - * - * 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.qi4j.library.invocationcache; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Default implementation of InvocationCache. - */ -public class InvocationCacheMixin - implements InvocationCache -{ - private final Map<String, Object> cachedValues = new ConcurrentHashMap<>(); - - @Override - public Object setCachedValue( String name, Object aResult ) - { - return cachedValues.put( name, aResult ); - } - - @Override - public Object cachedValue( String name ) - { - return cachedValues.get( name ); - } - - @Override - public Object removeCachedValue( String name ) - { - return cachedValues.remove( name ); - } - - @Override - public void clearCachedValues() - { - cachedValues.clear(); - } - - @Override - public int currentCacheSize() - { - return cachedValues.size(); - } -} http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/c24b54ec/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/ReturnCachedValueConcern.java ---------------------------------------------------------------------- diff --git a/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/ReturnCachedValueConcern.java b/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/ReturnCachedValueConcern.java index 8c0d2d2..9ad0d3f 100644 --- a/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/ReturnCachedValueConcern.java +++ b/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/ReturnCachedValueConcern.java @@ -21,6 +21,7 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Arrays; import org.qi4j.api.common.AppliesTo; +import org.qi4j.api.common.Optional; import org.qi4j.api.concern.ConcernOf; import org.qi4j.api.injection.scope.Invocation; import org.qi4j.api.injection.scope.This; @@ -33,34 +34,29 @@ public class ReturnCachedValueConcern extends ConcernOf<InvocationHandler> implements InvocationHandler { - @This + @This @Optional private InvocationCache cache; - @Invocation - private Method method; @Override public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { - // Try cache - String cacheName = method.getName(); - if( args != null ) + boolean voidReturnType = method.getReturnType().equals( Void.TYPE ); + if( cache != null || voidReturnType ) { - cacheName += Arrays.asList( args ); - } - Object result = cache.cachedValue( cacheName ); - if( result != null ) - { - if( result == Void.TYPE ) + // Try cache + String cacheName = method.getName(); + if( args != null ) { - return null; + cacheName += Arrays.asList( args ); } - else + Object result = cache.cachedValue( cacheName ); + if( result != null ) { return result; } } - // No cached value found - call method + // No cached value found or no InvocationCache defined - call method return next.invoke( proxy, method, args ); } } http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/c24b54ec/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/ReturnCachedValueOnExceptionConcern.java ---------------------------------------------------------------------- diff --git a/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/ReturnCachedValueOnExceptionConcern.java b/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/ReturnCachedValueOnExceptionConcern.java index 400baf5..1287b39 100644 --- a/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/ReturnCachedValueOnExceptionConcern.java +++ b/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/ReturnCachedValueOnExceptionConcern.java @@ -42,32 +42,29 @@ public class ReturnCachedValueOnExceptionConcern public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { - try + boolean voidReturnType = method.getReturnType().equals( Void.TYPE ); + if( cache != null || voidReturnType ) // Skip if void return type or no InvocationCache has been defined. { - // Invoke method - return next.invoke( proxy, method, args ); - } - catch( Exception e ) - { - // Try cache String cacheName = method.getName(); if( args != null ) { cacheName += Arrays.asList( args ); } - Object result = cache.cachedValue( cacheName ); - if( result != null ) + try + { + // Invoke method + Object result = next.invoke( proxy, method, args ); + // update cache + cache.setCachedValue( cacheName, result ); + return result; + } + catch( Exception e ) { - if( result == Void.TYPE ) - { - return null; - } - else - { - return result; - } + // Try cache + return cache.cachedValue( cacheName ); } - throw e; } + // if no InvocationCache is present. + return next.invoke( proxy, method, args ); } } http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/c24b54ec/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/SimpleInvocationCacheMixin.java ---------------------------------------------------------------------- diff --git a/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/SimpleInvocationCacheMixin.java b/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/SimpleInvocationCacheMixin.java new file mode 100644 index 0000000..5897412 --- /dev/null +++ b/libraries/invocation-cache/src/main/java/org/qi4j/library/invocationcache/SimpleInvocationCacheMixin.java @@ -0,0 +1,69 @@ +/* + * Copyright 2007 Rickard Ãberg. + * + * 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.qi4j.library.invocationcache; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Simple implementation of InvocationCache. + * <p> + * This {@link InvocationCache} should typically not be used at all, and only serves as an + * example. The @{code cachedValues} member is never emptied, so it constitutes a memory leak + * if the method arguments keep changing. + * </p> + * <p> + * <b>IMPORTANT: Only use this is you know that only a small set of arguments are used into your + * method(s).</b> + * </p> + */ +public class SimpleInvocationCacheMixin + implements InvocationCache +{ + private final Map<String, Object> cachedValues = new ConcurrentHashMap<>(); + + @Override + public Object setCachedValue( String name, Object aResult ) + { + return cachedValues.put( name, aResult ); + } + + @Override + public Object cachedValue( String name ) + { + return cachedValues.get( name ); + } + + @Override + public Object removeCachedValue( String name ) + { + return cachedValues.remove( name ); + } + + @Override + public void clearCachedValues() + { + cachedValues.clear(); + } + + @Override + public int currentCacheSize() + { + return cachedValues.size(); + } +} http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/c24b54ec/libraries/invocation-cache/src/test/java/org/qi4j/library/invocationcache/DocumentationSupport.java ---------------------------------------------------------------------- diff --git a/libraries/invocation-cache/src/test/java/org/qi4j/library/invocationcache/DocumentationSupport.java b/libraries/invocation-cache/src/test/java/org/qi4j/library/invocationcache/DocumentationSupport.java new file mode 100644 index 0000000..782ce76 --- /dev/null +++ b/libraries/invocation-cache/src/test/java/org/qi4j/library/invocationcache/DocumentationSupport.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 + * + * 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.qi4j.library.invocationcache; + +import org.qi4j.bootstrap.AssemblyException; +import org.qi4j.bootstrap.LayerAssembly; +import org.qi4j.bootstrap.ModuleAssembly; +import org.qi4j.bootstrap.layered.ModuleAssembler; + +public class DocumentationSupport +{ +// START SNIPPET: composite + public interface ExpensiveOperation + { + @Cached + double compute( double... arguments ); + } +// END SNIPPET: composite + +// START SNIPPET: assembly + public class ExpensiveModuleAssembler + implements ModuleAssembler{ + + @Override + public ModuleAssembly assemble( LayerAssembly layer, ModuleAssembly module ) + throws AssemblyException + { + module.services( ExpensiveOperation.class ) + .withMixins( SimpleInvocationCacheMixin.class ) + .withConcerns( ReturnCachedValueConcern.class ); + return module; + } + } +// END SNIPPET: assembly +}
