# Mittels Annotations Methoden "erweitern"?



## dunhillone (8. Mrz 2010)

Hi

Ich versuche gerade etwas mit Annotations zu basteln. Die Idee wäre, dass für eine Methode eine sog. @SecurityCheck Annotation hinzugefügt werden kann. Diese sollte bewirken, dass vor oder nach dem Aufruf der Methode ein check auf die aktuellen Userdaten gemacht wird. 

Irgendwie krieg ich es nicht so richtig hin. Das deklarieren der Annotation ist simpel, jedoch die Implementierung??


Hier mal der Code bisher..

Deklaration:

```
@Retention( RetentionPolicy.RUNTIME ) 
@Target(ElementType.METHOD)
public @interface SecurityCheck {
	/**
	 * Name of the user variable. Needs to be present in the methods attributes.
	 */
	String userVariable();
	
	/**
	 * When should the security check be done? Before or after the call (after means the return value is checked)
	 */
	SecurityCheckType type() default SecurityCheckType.BEFORE;
	
	/**
	 * For what role should be tested? (read/write/delete)
	 */
	Role role() default Role.READ;
}
```

Und Klasse welche die Annotation braucht:


```
public class BusinessService {

	/**
	 * Delete a business object.
	 */
	@SecurityCheck(role=Role.DELETE,type=SecurityCheckType.BEFORE,userVariable="securityObject")
	public void deleteBusinessObject(IBusinessObject businessObject, ISecurityObject securityObject) {
		
		// this body should not be executed until the security check is done...
		
	}
}
```

Was jetzt halt noch fehlt ist die implementierung der Annotation und wie und was ich wo im Programmablauf einfügen muss, um
die Annotation auch zu verwenden.

Bitte klärt mich auf, auch bezüglich, ob mein Vorhaben Sinnvoll ist..


----------



## madboy (8. Mrz 2010)

Willst du das alles selber machen oder hast du ein Problem mit einer Library, die die annotations für dich auslesen soll?
Wie dem auch sei: für mich sieht das sehr nach AOP aus und da gibt es ein paar Frameworks, sodass du das Rad nicht neu erfinden musst. Suche mal nach aspectj oder allgemeiner nach "Java aop", da sollte was dabei sein.


----------



## dunhillone (8. Mrz 2010)

madboy hat gesagt.:


> Willst du das alles selber machen oder hast du ein Problem mit einer Library, die die annotations für dich auslesen soll?
> Wie dem auch sei: für mich sieht das sehr nach AOP aus und da gibt es ein paar Frameworks, sodass du das Rad nicht neu erfinden musst. Suche mal nach aspectj oder allgemeiner nach "Java aop", da sollte was dabei sein.



Ursprünglich wollte ich es mal selber implementieren. Mit AOP kenne ich mich nicht aus. Wäre wahrscheinlich auch zu spät, der Rest des Projektes ist normales Java. Da jetzt umzustellen wegen einem kleinen Teil.. denke das lohnt sich nicht.

Ich wollte jetzt mal mit eignen Annotations was basteln.. Keine Ahnung.. vileicht habe ich auch das Prinzip noch nicht ganz gerafft

Wenn nicht dann lassen ich es so wies ist. Gibt dann halt pro Methode ein par Zeilen Code mehr indem ich die Methoden für den Security Check direkt aufrufe..


----------



## Landei (8. Mrz 2010)

Soweit ich (und Wikipedia) weiss ist AspectJ ein Superset von Java, jedes gültige Java-Programm ist auch ein gültiges AspectJ-Programm.


----------



## byte (8. Mrz 2010)

Funktionserweiterungen mittels eigener Annotations lassen sich mit AspectJ ziemlich leicht realisieren. Hier ein Beispiel für obige SecurityCheck Annotation:


```
@Aspect
public class SecurityManager {
    
    @Before(
            value="execution(public * de.foobar.secured..*.*(..)) && @annotation(secured)",
            argNames="secured")
    public void doAccessCheck(SecurityCheck secured) {
        // Prüfen, ob Nutzer die nötige Rolle hat. Falls nein: Exception werfen
    }
    
}
```

Der Pointcut würde alle öffentlichen Methoden erweitern, die sich im Package de.foobar.secured befinden und mit SecurityCheck annotiert sind.

Man hat natürlich noch viel mehr Möglichkeiten (@Around, @After, ...) und kommt bei Bedarf auch an das Objekt und die Parameter ran, falls man das braucht.


----------



## dunhillone (8. Mrz 2010)

Danke schonmal.. sieht doch schon sehr interessant aus.

Weiss jemand gerade wie man auf die Annotation values zugreifen kann?


----------



## byte (8. Mrz 2010)

Steht doch im Code! Das Annotation Objekt wird als Parameter übergeben. Da kannst Du alle Informationen auslesen, also in Deinem Fall z.B. die Rollen.


----------



## kay73 (8. Mrz 2010)

Hier ist eine alternative Implementierung dieser Auswertung von Annotationen.

Ich habe damals etwas Vergleichbares implementiert aber habe mich gegen AspectJ entschieden, weil die einzige Möglichkeit, die Ausführung des geschützten Codes zu verhindern ist, eine 
	
	
	
	





```
Exception
```
 zu werfen. Außerdem braucht es einen Weaver, der zusätzlich nach dem Compiler laufen muss. Außerdem wird AspectJ eigentlich nur als Eclipse-Plugin vernünftig unterstützt.

Wenn man auf die ausgefeilteren Features von AspectJ verzichten kann und die CGLIB verwendet, hat man IMHO deutlich interessantere und flexiblere Möglichkeiten, z. B. kann man den Methodenaufruf sauber auf ein alternatives Objekt umlenken, beliebige andere Werte zurückgeben oder eben per 
	
	
	
	





```
Exception
```
 aussteigen, wenn es sein muss.

Hier ist ein Beispiel, wie man einer Klasse zur Laufzeit beliebige neue Eigenschaften verpassen und diese nach der Erzeugung innerhalb Ereignisbehandlung beim Antreffen der Annotation nutzen kann. AspectJ kann das auch, allerdings nicht zur Laufzeit.

Man beachte, dass die Klasse 
	
	
	
	





```
MyClassToBeSecured
```
 niemals 
	
	
	
	





```
RiskLevel
```
 implementiert, aber die neue Eigenschaft trotzdem beim Antreffen der Annotation 
	
	
	
	





```
SecurityCheck
```
 ausgewertet wird. Derjenige, der 
	
	
	
	





```
MyClassToBeSecured
```
 implementiert, muss gar nicht wissen, dass es 
	
	
	
	





```
RiskLevel
```
 gibt.

Das Beispiel braucht die cglib-2.2.jar-Bibliothek im Klassenpfad.

```
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface SecurityCheck {
    int riskLevel() default 10;
}

class MyClassToBeSecured {

    @SecurityCheck(riskLevel=20)
    public void higlySecureOperation() {
        System.out.println("higlySecureOperation!");
    }

    @SecurityCheck(riskLevel=50)
    public void normalOperation() {
        System.out.println("normalOperation...");
    }
}

public class SecurityDemo {

    public static void main(String [] args) {

        MyClassToBeSecured ops = SecureProxyFactory.createSecureProxy(MyClassToBeSecured.class);

        ((RiskLevel)ops).setRiskLevel(25);
        ops.higlySecureOperation();
        ops.normalOperation();

        ((RiskLevel)ops).setRiskLevel(15);
        ops.higlySecureOperation();

        ((RiskLevel)ops).setRiskLevel(-1);
        ops.normalOperation();
    }
}

interface RiskLevel {
    public void setRiskLevel(int level);
}

class SecureProxyFactory {
    
    private static class RiskLevelMethodInterceptor implements MethodInterceptor {
        
        private int riskLevel;
        
        public Object intercept(final Object o, final Method method, final Object[] os, final MethodProxy mp) throws Throwable {

            final String methodName = method.getName();

            if(methodName.equals("setRiskLevel")) {
                riskLevel=(Integer)os[0];
                return null;
            }

            if (method.isAnnotationPresent(SecurityCheck.class)) {

                int methodRiskLevel = method.getAnnotation(SecurityCheck.class).riskLevel();
                
                if(riskLevel > methodRiskLevel) {
                    System.out.println("Intercepted operation: "+methodName+", method risk: "+methodRiskLevel+", current risk: "+riskLevel+": Risk too high!");
                    return null;
                }

                if(riskLevel < 0) {
                    System.out.println(riskLevel+": Illegal risk level!");
                    throw new RuntimeException("Highest risk!");
                }
                
                System.out.println("Intercepted operation: "+methodName+", method risk: "+methodRiskLevel+", current risk: "+riskLevel+": Risk ok.");
            }

            return mp.invokeSuper(o, os);
        }
    }
    
    public static <T> T createSecureProxy(final Class<T> clazz) {
        
        final List<Class<?>> interfaces = new ArrayList<Class<?>>(Arrays.asList(clazz.getInterfaces()));
        interfaces.add(RiskLevel.class);
        
        return (T) Enhancer.create(clazz,
                interfaces.toArray(new Class<?>[]{}),
                new RiskLevelMethodInterceptor());
    }
}
```
Die Exception fliegt erst zuletzt:
	
	
	
	





```
Intercepted operation: higlySecureOperation, method risk: 20, current risk: 25: Risk too high!
Intercepted operation: normalOperation, method risk: 50, current risk: 25: Risk ok.
Exception in thread "main" java.lang.RuntimeException: Highest risk!
        at SecureProxyFactory$RiskLevelMethodInterceptor.intercept(SecurityDemo.java:81)
        at MyClassToBeSecured$$EnhancerByCGLIB$$14e3b52.normalOperation(<generated>)
        at SecurityDemo.main(SecurityDemo.java:47)
normalOperation...
Intercepted operation: higlySecureOperation, method risk: 20, current risk: 15: Risk ok.
higlySecureOperation!
-1: Illegal risk level!
```


----------



## byte (9. Mrz 2010)

kay73 hat gesagt.:


> Außerdem wird AspectJ eigentlich nur als Eclipse-Plugin vernünftig unterstützt.



Falsch. AspectJ wird z.B. auch durch Spring unterstützt, wenn man Load Time Weaving nutzt. Du kannst AspectJ problemlos ohne Eclipse OSGi verwenden. Einfach das Jar in den Classpath und fertig.



> Wenn man auf die ausgefeilteren Features von AspectJ verzichten kann und die CGLIB verwendet, hat man IMHO deutlich interessantere und flexiblere Möglichkeiten, z. B. kann man den Methodenaufruf sauber auf ein alternatives Objekt umlenken, beliebige andere Werte zurückgeben oder eben per Exception aussteigen, wenn es sein muss.



Das geht mit AspectJ auch alles. Wenn Du in einem @Before Advice eine Exception wirfst, dann wird die eigentliche Methode nicht mehr ausgeführt. Wenn Du einen @Around Advice benutzt, kannst Du problemlos auf ein anderes Objekt delegieren, statt die eigentliche Methode aufzurufen.


Übrigens: Wenn man kein Load Time Weaving benutzen will, kann man auch ganz einfach JDK Proxies verwenden. Dann braucht man gar keine externe Lib.


----------



## kay73 (9. Mrz 2010)

Around ging total an mir vorbei. Man lernt nie aus ;-)


----------



## dunhillone (10. Mrz 2010)

Das Thema ist erst mal nach hinten verschoben.. leider  es warten dringendere Angelegenheiten.

Habe mich jetzt aber in erster Instanz mal für AspectJ entschieden, auch weil die Application mal auf Spring laufen wird.

Danke für die Tips bis hierhin.


----------



## kay73 (10. Mrz 2010)

dunhillone hat gesagt.:


> Habe mich jetzt aber in erster Instanz mal für AspectJ entschieden, auch weil die Application mal auf Spring laufen wird.


Grundaetzlich parst Spring aber nur die @AspectJ-Annotationen und verwendet normalerweise Standard-Java-Proxies (wie byte u. a. vorschlaegt) oder alternativ CGLIB-Proxies (wie mein Code). Fuer echtes AspectJ ist Zusatzgefummel notwendig. Aber das wirst Du wahrscheinlich eh nicht brauchen.


----------

