2010-07-17 19 views
45

Estoy desarrollando una aplicación empresarial Java, actualmente haciendo cosas de seguridad Java EE para restringir el acceso de determinadas funciones a usuarios específicos. He configurado el servidor de aplicaciones y todo, y ahora estoy usando el RolesAllowed-anotación para asegurar los métodos:Usar el tipo Enum como un parámetro de valor para @ RolesAllowed-Annotation

@Documented 
@Retention (RUNTIME) 
@Target({TYPE, METHOD}) 
public @interface RolesAllowed { 
    String[] value(); 
} 

Cuando uso la anotación como éste, que funciona bien:

@RolesAllowed("STUDENT") 
public void update(User p) { ... } 

Pero esto no es lo que quiero, ya que tengo que usar una Cadena aquí, la refacturación se vuelve difícil y pueden ocurrir errores tipográficos. Entonces, en lugar de usar un String, me gustaría utilizar un valor Enum como parámetro para esta anotación. La enumeración es el siguiente:

public enum RoleType { 
    STUDENT("STUDENT"), 
    TEACHER("TEACHER"), 
    DEANERY("DEANERY"); 

    private final String label; 

    private RoleType(String label) { 
     this.label = label; 
    } 

    public String toString() { 
     return this.label; 
    } 
} 

así que traté de usar la enumeración como un parámetro de la siguiente manera:

@RolesAllowed(RoleType.DEANERY.name()) 
public void update(User p) { ... } 

Pero entonces me sale el siguiente error del compilador, aunque Enum.name sólo devuelve una cadena (que siempre es constante, ¿no?).

The value for annotation attribute RolesAllowed.value must be a constant expression`

La siguiente cosa que intenté fue agregar un final String adicional a mi enumeración:

public enum RoleType { 
    ... 
    public static final String STUDENT_ROLE = STUDENT.toString(); 
    ... 
} 

Pero esto también no funciona como un parámetro, lo que resulta en el mismo error del compilador:

// The value for annotation attribute RolesAllowed.value must be a constant expression 
@RolesAllowed(RoleType.STUDENT_ROLE) 

¿Cómo puedo lograr el comportamiento que quiero? Incluso implementé mi propio interceptor para usar mis propias anotaciones, lo cual es hermoso, pero hay demasiadas líneas de códigos para un pequeño problema como este.

RENUNCIA

Esta pregunta fue originalmente una pregunta Scala. Descubrí que Scala no es la fuente del problema, así que primero intento hacerlo en Java.

+1

Lo siento, esto es un poco irrelevante para resolver su problema, pero pensé que mencionaría que puede prescindir del argumento String para sus constructores enum si solo los va a tener igual a los que usted los nombra ; puede acceder al mismo valor llamando a .name() en la enumeración. Creo que el método toString() delega a name() de todos modos. – I82Much

+0

posible duplicado de [Cómo suministrar el valor de Enum a una anotación de una constante en Java] (http://stackoverflow.com/questions/13253624/how-to-supply-enum-value-to-an-annotation-from- a-constant-in-java) –

Respuesta

29

No creo que su enfoque de usar enumeraciones vaya a funcionar. He encontrado que el error del compilador se fue si cambiaba el campo STUDENT_ROLE en su último ejemplo de una cadena constante, en lugar de una expresión:

public enum RoleType { 
    ... 
    public static final String STUDENT_ROLE = "STUDENT"; 
    ... 
} 

Sin embargo, esto significa, entonces, que no se utilizarían los valores de enumeración en cualquier lugar, porque estarías usando las constantes de cadena en anotaciones en su lugar.

Me parece que sería mejor que su clase RoleType no contuviera más que un montón de constantes de cadena estáticas finales.


ver por qué su código no estaba compilando, tenía una mirada hacia el Java Language Specification (JLS).El JLS para annotations establece que para una anotación con un parámetro de tipo T y el valor V,

if T is a primitive type or String , V is a constant expression.

A constant expression incluye, entre otras cosas, se define

Qualified names of the form TypeName . Identifier that refer to constant variables

y una constant variable como

a variable, of primitive type or type String , that is final and initialized with a compile-time constant expression

+4

¡Gracias por su esfuerzo! Datos interesantes. Especialmente el último. Siempre pensé que las finales ya son constantes. Bien, esta es la razón por la cual no puede funcionar. Me parece que esta es la respuesta. Aunque no estoy muy contento con eso;) (Por cierto, realmente necesito el Enum, no solo para la anotación) – ifischer

+0

De hecho encontré con [un ejemplo] (http://java.dzone.com/articles/glassfish- 3-30 minutos) que declara 'interfaz pública Roles {String REGISTERED =" registered "; } 'y luego usa' @RolesAllowed ({Roles.REGISTERED}) '. Por supuesto, un ejemplo * no * usando 'enum' no significa que' enum' produce problemas, pero bueno ;-) – Arjan

7

Aquí ' s una solución que usa una interfaz adicional y una meta-anotación. He incluido una clase de utilidad para ayudar a hacer la reflexión para obtener los tipos de rol de un conjunto de anotaciones, y una pequeña prueba para ello:

/** 
* empty interface which must be implemented by enums participating in 
* annotations of "type" @RolesAllowed. 
*/ 
public interface RoleType { 
    public String toString(); 
} 

/** meta annotation to be applied to annotations that have enum values implementing RoleType. 
* the value() method should return an array of objects assignable to RoleType*. 
*/ 
@Retention(RetentionPolicy.RUNTIME) 
@Target({ANNOTATION_TYPE}) 
public @interface RolesAllowed { 
    /* deliberately empty */ 
} 

@RolesAllowed 
@Retention(RetentionPolicy.RUNTIME) 
@Target({TYPE, METHOD}) 
public @interface AcademicRolesAllowed { 
    public AcademicRoleType[] value(); 
} 

public enum AcademicRoleType implements RoleType { 
    STUDENT, TEACHER, DEANERY; 
    @Override 
    public String toString() { 
     return name(); 
    } 
} 


public class RolesAllowedUtil { 

    /** get the array of allowed RoleTypes for a given class **/ 
    public static List<RoleType> getRoleTypesAllowedFromAnnotations(
      Annotation[] annotations) { 
     List<RoleType> roleTypesAllowed = new ArrayList<RoleType>(); 
     for (Annotation annotation : annotations) { 
      if (annotation.annotationType().isAnnotationPresent(
        RolesAllowed.class)) { 
       RoleType[] roleTypes = getRoleTypesFromAnnotation(annotation); 
       if (roleTypes != null) 
        for (RoleType roleType : roleTypes) 
         roleTypesAllowed.add(roleType); 
      } 
     } 
     return roleTypesAllowed; 
    } 

    public static RoleType[] getRoleTypesFromAnnotation(Annotation annotation) { 
     Method[] methods = annotation.annotationType().getMethods(); 
     for (Method method : methods) { 
      String name = method.getName(); 
      Class<?> returnType = method.getReturnType(); 
      Class<?> componentType = returnType.getComponentType(); 
      if (name.equals("value") && returnType.isArray() 
        && RoleType.class.isAssignableFrom(componentType)) { 
       RoleType[] features; 
       try { 
        features = (RoleType[]) (method.invoke(annotation, 
          new Object[] {})); 
       } catch (Exception e) { 
        throw new RuntimeException(
          "Error executing value() method in " 
            + annotation.getClass().getCanonicalName(), 
          e); 
       } 
       return features; 
      } 
     } 
     throw new RuntimeException(
       "No value() method returning a RoleType[] type " 
         + "was found in annotation " 
         + annotation.getClass().getCanonicalName()); 
    } 

} 

public class RoleTypeTest { 

    @AcademicRolesAllowed({DEANERY}) 
    public class DeaneryDemo { 

    } 

    @Test 
    public void testDeanery() { 
     List<RoleType> roleTypes = RolesAllowedUtil.getRoleTypesAllowedFromAnnotations(DeaneryDemo.class.getAnnotations()); 
     assertEquals(1, roleTypes.size()); 
    } 
} 
3

¿Qué tal esto?

public enum RoleType { 
    STUDENT(Names.STUDENT), 
    TEACHER(Names.TEACHER), 
    DEANERY(Names.DEANERY); 

    public class Names{ 
     public static final String STUDENT = "Student"; 
     public static final String TEACHER = "Teacher"; 
     public static final String DEANERY = "Deanery"; 
    } 

    private final String label; 

    private RoleType(String label) { 
     this.label = label; 
    } 

    public String toString() { 
     return this.label; 
    } 
} 

Y en la anotación que se puede utilizar como es

@RolesAllowed(RoleType.Names.DEANERY) 
public void update(User p) { ... } 

Un poco interés, por cualquier modificación, tenemos que cambiar en dos lugares. Pero dado que están en el mismo archivo, es poco probable que se pierda. A cambio, estamos obteniendo el beneficio de no usar cadenas sin formato y evitar el mecanismo sofisticado.

¿O esto suena totalmente estúpido? :)

+0

Gracias, realmente me gusta esto y lo usaré. Llegué a esta pregunta originalmente porque estoy buscando una manera más clara de especificar la propiedad 'nombre' en la anotación '@ JsonSubTypes.Type' de Jackson, de tal manera que las enumeraciones que definen esos nombres lógicos se puedan usar en la anotación como además de estar disponible para otras partes de la aplicación. – Trevor

+0

Me alegro de que le haya gustado @Trevor – Samiron

Cuestiones relacionadas