Repository: polygene-java Updated Branches: refs/heads/develop 6d8d97ec0 -> 27cad2880
POLYGENE-133 - implement, test and document @Concerns and @SideEffects on methods. Project: http://git-wip-us.apache.org/repos/asf/polygene-java/repo Commit: http://git-wip-us.apache.org/repos/asf/polygene-java/commit/27cad288 Tree: http://git-wip-us.apache.org/repos/asf/polygene-java/tree/27cad288 Diff: http://git-wip-us.apache.org/repos/asf/polygene-java/diff/27cad288 Branch: refs/heads/develop Commit: 27cad28800c8300f931982937879f84566cfe795 Parents: 6d8d97e Author: niclas <[email protected]> Authored: Mon Apr 10 13:51:45 2017 +0800 Committer: niclas <[email protected]> Committed: Mon Apr 10 13:51:45 2017 +0800 ---------------------------------------------------------------------- core/api/src/docs/concern.txt | 13 ++- core/api/src/docs/sideeffect.txt | 57 +++++++++++- .../bootstrap/CompositeAssemblyImpl.java | 76 ++++++++++------ .../runtime/concerns/MethodConcernsTest.java | 94 ++++++++++++++++++++ 4 files changed, 213 insertions(+), 27 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/polygene-java/blob/27cad288/core/api/src/docs/concern.txt ---------------------------------------------------------------------- diff --git a/core/api/src/docs/concern.txt b/core/api/src/docs/concern.txt index 02354e3..bd6f5dd 100644 --- a/core/api/src/docs/concern.txt +++ b/core/api/src/docs/concern.txt @@ -26,8 +26,17 @@ To create a concern, you need to create a class that, You are allowed to modify both the in-arguments as well as the returned value, including throw exceptions if that is suitable, perhaps for post condition checks. +== Applicability == +Concerns are applied to composite types in several ways; + + * @Concerns annotation on the Mixin Type. + * withConcerns() assembly instruction at bootstrap. + * In custom annotations to be applied to either Mixin Types or methods on Mixin Types. + * @Concerns annotation directly on a method. + + == Typed Concern == -As mentioned above, concerns that implements the _Mixin Type_ are called *Typed Mixins*. They are more common in the +As mentioned above, concerns that implements the _Mixin Type_ are called *Typed Concerns*. They are more common in the business domain, and can be used for many important things in the domain model, such as checking post conditions (i.e. ensure that the state in the entire composite is valid), coordinating services, handling events and much more. @@ -51,6 +60,8 @@ control over this selection process, via several mechanisms. * an annotation type that must be given on the method. * Concerns are added only to composites that declares the Concern, either in * the Composite Type, or + * on any method of the Composite Type, or + * on an annotation that is in turn declared on a Composite Type method * during assembly in the withConcerns() method. This means that we can make the following three samples of concerns. First the generic concern that is added to the methods http://git-wip-us.apache.org/repos/asf/polygene-java/blob/27cad288/core/api/src/docs/sideeffect.txt ---------------------------------------------------------------------- diff --git a/core/api/src/docs/sideeffect.txt b/core/api/src/docs/sideeffect.txt index 127ad11..ba12387 100644 --- a/core/api/src/docs/sideeffect.txt +++ b/core/api/src/docs/sideeffect.txt @@ -12,4 +12,59 @@ ////////////////////// [[core-api-sideeffect,SideEffect]] -= SideEffect = \ No newline at end of file += SideEffect = + +SideEffects have no equivalent in Aspect Oriented Programming. They are executed AFTER the method invocation, and +they are potentially concurrent with the method invocation itself. The SideEffect receives the incoming method arguments +and can query the result of the method call by accessing the +next+ field. SideEffects can NOT influence the method +call in any way, and both return values from the SideEffect, as well as any exceptions thrown, will be ignored. + +To create a sideeffect, you need to create a class that, + + * implements the Mixin Type (Typed SideEffects) or java.lang.reflect.InvocationHandler (Generic SideEffects), + * extend SideEffectOf (Typed Concerns) or GenericSideEffect (Generic SideEffects) [1] + +You are allowed to modify both the in-arguments as well as the returned value, including throw exceptions if that is +suitable, perhaps for post condition checks. + +== Applicability == +SideEffects are applied to composite types in several ways; + + * @SideEffects annotation on the Mixin Type. + * withSideEffects() assembly instruction at bootstrap. + * @SideEffects annotation of custom annotations to be applied to either Mixin Types or methods on Mixin Types. + * @SideEffects annotation directly on a method. + +== Typed SideEffect == +As mentioned above, side effects that implements the _Mixin Type_ are called *Typed SideEffects*. + +A Typed SideEffect doesn't have to implement all the methods in the Mixin Type. By making the class abstract and only +implementing the methods of interest, Polygene⢠runtime will subclass the side effect (otherwise not valid for the +JVM/compiler), but the generated methods will never be invoked. + +== Generic SideEffect == +Generic SideEffects implement the +java.lang.reflect.InvocationHandler+ and can potentially serve any method it is +applied to. Generic SideEffects will be added to all methods that the AppliesToFilter evaluates to true. By default, +that is all methods. + +AppliesToFilters is a mechanism to limit, or direct, which methods that the concern should be added to. You have full +control over this selection process, via several mechanisms. + + * @AppliesTo annotation can be put on the side effect, with either; + * an interface for which the methods should be wrapped, or + * an AppliesToFilter implementation that is consulted during building the invocation stack, or + * an annotation type that must be given on the method. + * SideEffects are added only to composites that declares the SideEffect, either in + * the Composite Type, or + * on any method of the Composite Type, or + * on an annotation that is in turn declared on a Composite Type method + * during assembly in the withSideEffects() method. + +== Invocation Order == +The invocation order of SideEffects is UNDEFINED, and one MUST NOT rely on SideEffects executing in any particular order. +They MAY be concurrent and outside the thread that executed the method, so the SideEffect can also not depend on +the UnitOfWork that may be observed as present. + +To be clear; the method call to the SideEffect is NOT its own Polygene-controlled invocation stack, and any annotations +on the SideEffect methods will be ignored (or it is a bug). That means that IF the SideEffect needs a UnitOfWork it +either needs to manage one explicitly or call out to a service that has the @UnitOfWorkPropagation annotation. http://git-wip-us.apache.org/repos/asf/polygene-java/blob/27cad288/core/runtime/src/main/java/org/apache/polygene/runtime/bootstrap/CompositeAssemblyImpl.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/polygene/runtime/bootstrap/CompositeAssemblyImpl.java b/core/runtime/src/main/java/org/apache/polygene/runtime/bootstrap/CompositeAssemblyImpl.java index 2c75e21..fa71481 100644 --- a/core/runtime/src/main/java/org/apache/polygene/runtime/bootstrap/CompositeAssemblyImpl.java +++ b/core/runtime/src/main/java/org/apache/polygene/runtime/bootstrap/CompositeAssemblyImpl.java @@ -271,7 +271,7 @@ public abstract class CompositeAssemblyImpl } catch( Exception e ) { - System.out.println("NICLAS 2: " + e.getClass() + " - " + e.getMessage()); + System.out.println( "NICLAS 2: " + e.getClass() + " - " + e.getMessage() ); exceptions.add( e ); } } @@ -650,21 +650,19 @@ public abstract class CompositeAssemblyImpl } } ); - // Check annotations on method that have @Concerns annotations themselves for( Annotation annotation : method.getAnnotations() ) { - @SuppressWarnings( "raw" ) - Concerns concerns = annotation.annotationType().getAnnotation( Concerns.class ); - if( concerns != null ) + if( annotation instanceof Concerns ) { - for( Class<?> concern : concerns.value() ) - { - if( helper.appliesTo( concern, method, types, mixinClass ) ) - { - ConcernModel concernModel = helper.getConcernModel( concern ); - addConcernOrRepositionIfExists( concernsFor, concernModel ); - } - } + // Check @Concerns annotations directly on methods + Concerns concerns = (Concerns) annotation; + addConcerns( method, mixinClass, concernsFor, concerns ); + } + else + { + // Check annotations on method that have @Concerns annotations themselves + Concerns concerns = annotation.annotationType().getAnnotation( Concerns.class ); + addConcerns( method, mixinClass, concernsFor, concerns ); } } @@ -678,6 +676,21 @@ public abstract class CompositeAssemblyImpl } } + private void addConcerns( Method method, Class<?> mixinClass, List<ConcernModel> concernsFor, Concerns concerns ) + { + if( concerns != null ) + { + for( Class<?> concern : concerns.value() ) + { + if( helper.appliesTo( concern, method, types, mixinClass ) ) + { + ConcernModel concernModel = helper.getConcernModel( concern ); + addConcernOrRepositionIfExists( concernsFor, concernModel ); + } + } + } + } + private void addConcernOrRepositionIfExists( List<ConcernModel> concernsFor, ConcernModel concernModel ) { // This remove/add is to allow re-ordering of the concerns @@ -719,21 +732,19 @@ public abstract class CompositeAssemblyImpl } } ); - // Check annotations on method that have @Concerns annotations themselves for( Annotation annotation : method.getAnnotations() ) { - @SuppressWarnings( "raw" ) - SideEffects sideEffects = annotation.annotationType().getAnnotation( SideEffects.class ); - if( sideEffects != null ) + if( annotation instanceof SideEffects ) { - for( Class<?> sideEffect : sideEffects.value() ) - { - if( helper.appliesTo( sideEffect, method, types, mixinClass ) ) - { - SideEffectModel sideEffectModel = helper.getSideEffectModel( sideEffect ); - addSideEffectOrRepositionIfExists( sideEffectsFor, sideEffectModel ); - } - } + // Check SideEffects annotation on method + SideEffects sideEffects = (SideEffects) annotation; + addSideEffects( method, mixinClass, sideEffectsFor, sideEffects ); + } + else + { + // Check annotations on method that have @SideEffects annotations themselves + SideEffects sideEffects = annotation.annotationType().getAnnotation( SideEffects.class ); + addSideEffects( method, mixinClass, sideEffectsFor, sideEffects ); } } @@ -747,6 +758,21 @@ public abstract class CompositeAssemblyImpl } } + private void addSideEffects( Method method, Class<?> mixinClass, List<SideEffectModel> sideEffectsFor, SideEffects sideEffects ) + { + if( sideEffects != null ) + { + for( Class<?> sideEffect : sideEffects.value() ) + { + if( helper.appliesTo( sideEffect, method, types, mixinClass ) ) + { + SideEffectModel sideEffectModel = helper.getSideEffectModel( sideEffect ); + addSideEffectOrRepositionIfExists( sideEffectsFor, sideEffectModel ); + } + } + } + } + private void addSideEffectOrRepositionIfExists( List<SideEffectModel> sideEffectsFor, SideEffectModel sideEffectModel ) http://git-wip-us.apache.org/repos/asf/polygene-java/blob/27cad288/core/runtime/src/test/java/org/apache/polygene/runtime/concerns/MethodConcernsTest.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/test/java/org/apache/polygene/runtime/concerns/MethodConcernsTest.java b/core/runtime/src/test/java/org/apache/polygene/runtime/concerns/MethodConcernsTest.java new file mode 100644 index 0000000..245c12f --- /dev/null +++ b/core/runtime/src/test/java/org/apache/polygene/runtime/concerns/MethodConcernsTest.java @@ -0,0 +1,94 @@ +/* + * 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.apache.polygene.runtime.concerns; + +import org.apache.polygene.api.concern.ConcernOf; +import org.apache.polygene.api.concern.Concerns; +import org.apache.polygene.api.mixin.Mixins; +import org.apache.polygene.api.sideeffect.SideEffectOf; +import org.apache.polygene.api.sideeffect.SideEffects; +import org.apache.polygene.bootstrap.AssemblyException; +import org.apache.polygene.bootstrap.ModuleAssembly; +import org.apache.polygene.test.AbstractPolygeneTest; +import org.junit.Test; + +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertThat; + +public class MethodConcernsTest extends AbstractPolygeneTest +{ + private static int count = 0; + + @Override + public void assemble( ModuleAssembly module ) + throws AssemblyException + { + module.values( SomeType.class ); + } + + @Test + public void givenTypedConcernWhenCallingMethodExpectConcernToBeInvoked() + throws Exception + { + SomeType value = valueBuilderFactory.newValue( SomeType.class ); + assertThat( value.doSomething( "abc" ), equalTo( "(...abc...)" ) ); + assertThat( count, equalTo(1) ); + } + + @Mixins( Mixin.class ) + public interface SomeType + { + @Concerns( ParenWrapConcern.class ) + @SideEffects( CountInvocationsSideEffect.class ) + String doSomething( String value ); + } + + public class Mixin implements SomeType + { + + @Override + public String doSomething( String value ) + { + return "..." + value + "..."; + } + } + + public static class ParenWrapConcern extends ConcernOf<SomeType> + implements SomeType + { + + @Override + public String doSomething( String value ) + { + return "(" + next.doSomething( value ) + ")"; + } + } + + public static class CountInvocationsSideEffect extends SideEffectOf<SomeType> + implements SomeType + { + @Override + public String doSomething( String value ) + { + count++; + return null; // side effect returns are ignored. + } + } +}
