Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
Re: [aspectj-users] Restricting visibility of a method via AspectJ

Hi Wim,

I like your idea with @RestrictedVIsibility.

In PatternTesting (http://patterntesting.sourceforge.net/) there are two annotations: @OnlyForTesting, which you can use to mark methods, which should be called only for testing. These methods can be only called from (JUnit) test methods or other methods which are marked with @OnlyForTesting (see http://patterntesting.sourceforge.net/09/check/ct/xref-test/patterntesting/check/ct/OnlyForTestingTest.html for an example). This use can be statically proofed:

    pointcut disallowedCalls() :
        !testCode() &&
            (call(@OnlyForTesting *..*.new(..))
            || call(@OnlyForTesting * *..*.*(..)))
        ;

    declare error : disallowedCalls() :
        "this call is only allowed from test code!";

But sometimes you have methods which are indirect called from your test code and are @PublicForTesting:

    pointcut belowTestMethods() :
        cflowbelow(execution(@Test * *..*()))
        || cflowbelow(execution(@Before * *..*()))
        || cflowbelow(execution(@BeforeClass * *..*()))
        || cflowbelow(execution(@After * *..*()))
        || cflowbelow(execution(@AfterClass * *..*()))
        || cflowbelow(execution(@OnlyForTesting * *..*()))
        || cflowbelow(execution(@OnlyForTesting *..new()))
        || cflowbelow(@within(OnlyForTesting))
        ;

    public pointcut applicationCode() :
        (call(@PublicForTesting * *..*(..))
            || call(@PublicForTesting *..new(..)))
        && !belowTestMethods()
        ;

Unfortunately this can be not used inside 'declare error' (because of cflowbelow). So it is used in an after-advice:

    after() : applicationCode()  {
        if (Assertions.enabled) {
            String caller = getCallerClassName();
            Signature sig = thisJoinPointStaticPart.getSignature();
            String self = sig.getDeclaringTypeName();
            assert self.equals(caller) :
                "only test methods or object itself may call "
                + JoinPointHelper.getAsShortString(thisJoinPoint);
        }
    }

As you can see it is only checked if assertions are enabled. So PatternTesting use not a RuntimeException here. An AssertionError will be thrown in case of a violation of incorrect use of the @PublicOnlyForTesting method.

regards
Oliver



Am 05.01.2010 um 20:51 schrieb Wim Deblauwe:

Hi,

I had the idea that it should be possible to use an annotation to declare custom visibility of a method that is really public. You could use this to annotate methods that have been made public for unit testing, or because you just need 1 particular class in another package to use it.

I just want to put down what I have got so far, maybe others can build on it or improve if they like the idea.

Idea #1: Restrict the calling of the public method to test code (assuming TestNG here)

1) Create an annotation called @TestingOnly and put that on any method that only should be called by testing code

2) The following aspect should do it:
public aspect IllegalTestOnlyMethodCallChecker {
 
    declare error: testCallOutsideTestCode(): "Don't call test methods outside test code!";
 
    pointcut clientCall() : call(@TestingOnly * *(..)) 
    && !within(@org.testng.annotations.Test *) 
    && !withincode(@org.testng.annotations.Test * *(..)) ;
    }

Idea #2: Restrict the calling of a public method to only being called from an approved class

1) Create an annotation @RestrictedVisibility

@Retention(RetentionPolicy.RUNTIME)
public @interface RestrictedVisibility {
    Class allowedClass();
}

2) Use the annotation like this:

public class DomainObject {

    @RestrictedVisibility(allowedClass = TestService.class)
    public void domainMethod()  {
       
    }
}

This would mean that only TestService is allowed to call the method 'domainMethod'

3) Use this aspect

public aspect RestrictedVisibilityAspect {

    pointcut callToRestricted(Object targetObject, Object callingObject):call( @RestrictedVisibility * *(..) )
                                                            && target(targetObject)
                                                            && this(callingObject);
   
          
    before(Object targetObject, Object callingObject) : callToRestricted(targetObject, callingObject)
    {
        System.out.println( thisJoinPointStaticPart.getSignature() );
        System.out.println(targetObject);
        System.out.println(callingObject);
       
        Method m = ((MethodSignature)thisJoinPointStaticPart.getSignature()).getMethod();
        RestrictedVisibility rv = m.getAnnotation(RestrictedVisibility.class);
        if( rv.allowedClass().equals( callingObject.getClass() ))
        {
            System.out.println( "It is ok!");
        }
        else
        {
            System.out.println( "This call is not allowed!");
        }
    };
}

Note that I was not able to use declare error anymore due to the use of this() and target(). You should ofcourse throw a RunTimeException instead of just printing something like I did here.

Is it possible to avoid the reflection and casting?
Is anybody else using an Aspect simular to this?

regards,

Wim


_______________________________________________
aspectj-users mailing list
aspectj-users@xxxxxxxxxxx
https://dev.eclipse.org/mailman/listinfo/aspectj-users

-- 
Oliver Böhm




Back to the top