O EsfingeMETADATA é um meta-framework extensível para leitura e validação de metadados, que visa a simplificar e auxiliar principalmente o desenvolvimento de frameworks baseados em metadados.Além disso, também pode ser utilizado em aplicações baseadas em anotações customizadas.
Como regra geral, é necessária a adição dos arquivos JAR do Esfinge Metadata com as seguintes dependências: commons-beanutils-1.9.2.jar commons-collections-3.2.2.jar commons-logging-1.2.jar
O Esfinge Metadata usa como padrão o Maven para gerenciar as dependências. Por isso você deve colocar o artefato do Esfinge Metadata, o maven se encarrega de baixar as dependências que serão usadas.
<dependency> <groupId>net.sf.esfinge</groupId> <artifactId>metadata</artifactId> <version>1.5.6</version> </dependency>
O Esfinge Metadata possui uma funcionalidade que recupera e localiza anotações. De acordo com configurações feitas nas anotações que estão sendo procuradas, é possível que sejam buscadas em outros lugares relacionados ao elemento, como, por exemplo, dentro de outras anotações que ele possui.
Para localizar os metadado use o método AnnotationFinder.findAnnotation() , este vai retornar uma lista de Anotações.
Exemplo de Localização de Metadatos:Em um primeiro momento criamos a anotação chamada @AnnotationInElement .
@Retention(RetentionPolicy.RUNTIME) public @interface AnnotationInElement { }
Em seguida criamos uma classe que usa a anotação criada anteriormente, neste exemplo chamaremos de ElementWithMetadata .
@AnnotationInElement public class ElementWithMetadata { }
E depois criamos a busca por essa anotação no codigo usando o metodo estático AnnotationFinder.findAnnotation() que recebe como parametros um elemento com as Anotações neste caso a classe ElementWithMetadata e a anotação alvo que existe o elemento, neste caso a annotação @AnnotationInElement e tem como retorno uma lista de anotações, em caso falso ele vai retornar uma lista com zero posições.
@Test public void metadataLocatorTestWithAnnotation(){
List<Annotation> annotationList = AnnotationFinder.findAnnotation(ElementWithMetadata.class, AnnotationInElement.class); ; assertTrue(annotationList.size()>0); assertTrue(annotationList.get(0) instanceof AnnotationInElement);
}
Esta anotação configura quando uma anotação pode ser definida dentro de outra. Quando uma anotação com essa configuração é pesquisada, a implementação da API deve procurá-la dentro de cada uma das anotações do elemento de destino;
Exemplo:
Como exemplo em primeiro lugar, criamos uma anotação contendo @SearchInsideAnnotations que diz que a anotação está dentro de outra anotação. Neste caso a anotação @AnnotationInAnnotation está anotada com a @SearchInsideAnnotations
@Retention(RetentionPolicy.RUNTIME) @SearchInsideAnnotations public @interface AnotationInAnotation { }
Em seguida, criamos uma anotação que contem a anotação criada anteriormente, e tambem colocamos a anotação @SearchInsideAnnotations .
@Retention(RetentionPolicy.RUNTIME) @AnotationInAnotation @SearchInsideAnnotations public @interface AnotationInElement { }
Criamos uma classe que contem a anotação criada anteriormente.
@AnotationInElement public class ElementWithMetadata { }
Por ultimo criamos a busca pela anotação @AnnotationInAnnotation na classe ElementWithMetadata este vai verificar se o @AnnotationInAnnotation existe na classe existe no elemento anotado ou em anotações do elemento anotado.
@Test public void metadataLocatorTestWithAnnotation() { List<Annotation> annotationList = AnnotationFinder.findAnnotation(ElementWithMetadata.class, AnotationInAnotation.class); assertTrue(annotationList.size()>0); assertTrue(annotationList.get(0) instanceof AnotationInAnotation); }
Esta anotação configura quando uma anotação pode ser definida no escopo do seu elemento de inclusão. Quando uma anotação com essa configuração é pesquisada em um método, se não for encontrada, a implementação da API deve verificar se ela está presente em sua classe;
Exemplo:
Criemos uma anotação para anotar a classe, contendo a
anotação @SearchOnEnclosingElements,
que é a anotação responsavel
por dizer em que nivel a anotação está na classe.
@Retention(RetentionPolicy.RUNTIME) @SearchOnEnclosingElements public @interface AnnotationInClass {
}
Em seguida criamos uma classe annotada com a anotação criada anteriormente, com um atributo ou metodo, neste exemplo foi criado com um atributo.
@AnnotationInClass public class ClassWithAnnotation { public Object selectedElement; }
Em seguida executamos o AnnotationFinder.findAnnotation() este caso de teste, vai retornar uma lista de anotações contendo a anotação @AnnotationInClass esta anotação vai retorar um elemento de nivel acima do elemento que está sendo buscado.
@Test public void testWithAnnotation() throws NoSuchFieldException, SecurityException { List<Annotation> annotationList =
AnnotationFinder.findAnnotation(ClassWithAnnotation.class.getDeclaredField("selectedElement"),
AnnotationInClass.class); assertTrue(annotationList.size()>0); assertTrue(annotationList.get(0) instanceof AnnotationInClass); }
@SearchOnAbstractions public @interface AnnotedInterface { }Uma interface anotada com a anotação criada anteriormente.
@AnnotedInterface public interface InterfaceWithAnnotation { }Uma classe que implementa a interface que contem a anotação
public class ValidClass implements InterfaceWithAnnotation { }Em seguida executamos o AnnotationFinder.findAnnotation() este caso de teste, vai retornar uma lista de anotações contendo a anotação @AnnotedInterface esta anotação vai retorar um elemento anotado na abstração e pode ser utilizado com os outros locators.
public class SearchOnAbstractionsTest { @Test public void test() { List<Annotation> annotationList = AnnotationFinder.findAnnotation(ValidClass.class, AnnotedInterface.class); assertTrue(annotationList.size()>0); assertTrue(annotationList.get(0) instanceof AnnotedInterface); } }
Apesar das anotações poderem ser recuperadas diretamente de um elemento, normalmente as informações contidas nelas são todas colocadas em um objeto que armazena essas informações para a aplicação poder acessar futuramente. Essa classe é chamada de Metadata Container e isso é um padrão de projeto. Essa funcionalidade do Esfinge Metadata faz o mapeamento de anotações para uma classe que armazena essas informações, poupando o trabalho de leitura dos metadados.
@ContainerFor(ContainerTarget.TYPE) public class ContainerClass {
@ElementName private String elementName;
@ContainsAnnotation(AnnotationInClass.class) private boolean existAnnotation; @ReflectionReference private Class<?> reference; @AnnotationProperty(annotation = AnnotationInClass.class, property ="elementInAnnotation") private String elementInAnnotation; public String getElementInAnnotation() { return elementInAnnotation; } public void setElementInAnnotation(String elementInAnnotation) { this.elementInAnnotation = elementInAnnotation; }
public String getElementName() { return elementName; } public void setElementName(String elementName) { this.elementName = elementName; } public boolean isExistAnnotation() { return existAnnotation; } public void setExistAnnotation(boolean existAnnotation) { this.existAnnotation = existAnnotation; } public Class<?> getReference() { return reference; } public void setReference(Class<?> reference) { this.reference = reference; } }
@Retention(RetentionPolicy.RUNTIME) public @interface AnnotationInClass{ { String elementInAnnotation(); }Criaremos tambem a classe que vai ser utilizada pelo AnnotationReader, ela possui as informações da classe @AnnotationInClass, que neste caso ela passa por parametro uma string com um valor.
@AnnotationInClass(elementInAnnotation="valueOfAnnotation") public class ClassForReader { }
@Test public void testAnnotationsBasic() throws Exception { ContainerClass container; AnnotationReader annotationReader = new AnnotationReader(); container = annotationReader.readingAnnotationsTo(ClassForReader.class, ContainerClass.class); assertTrue(container.isExistAnnotation()); assertEquals(ClassForReader.class.getName(), container.getElementName()); assertEquals(ClassForReader.class, container.getReference()); assertEquals("valueOfAnnotation", container.getElementInAnnotation()); }
Anotação | Elemento Alvo | Uso |
---|---|---|
@ContainsAnnotation | Classe,metodo, field. | Esta anotação de Fields verifica se existe a anotação selecionada no elemento alvo. Ele contem apenas o método value() que retorana se a anotação selecionada existe, esta anotação deve ser usada em tipos primitivos boolean ou classes do tipo Boolean. |
@AnnotationProperty | Metodo,field. | Esta anotação verifica se existe uma anotação anotada na classe alvo, e alem disso verifica, se esta existe uma propriedade com um nome descrito. |
@ElementName | Classe,metodo, field. | Esta anotação de field pega o nome do elemento que foi anotado e passa para o field do container. |
@ReflectionReference | Classe,metodo, field. | Esta anotação de field pega a classe do elemento que foi anotado e passa para o field que foi anotado com esse elemento. |
São anotações usadas para leitura e execução em elementos avançado perante o Metadata Container, que são usados para leitura e processamento de elementos como elementos e metodos.
Em um primeiro momento criaremos um container de classe, armazena as informações de classe, atributos e metodos, foi criado os metodos getAnnotedMethod() e getAnnotedField() para facilitar as criação de caso de teste. Neste caso temos as anotações @ProcessMethods que pega as informações dos metodos e armazena em um container de metodos, e o @AllMethodWith que armazena as anotações anotadas em um container de metodos, enquanto o @ProcessFields armazena todos os elementos de atributos em um container, e o @AllFieldsWith armazena os atributos com determinada anotação em um container de Atributos, todos eles podem ser utilizado como Map, Set e List.@ContainerFor(ContainerTarget.TYPE) public class ContainerClass { @ElementName private String elementName; @ReflectionReference private Class<?> reference; @ProcessMethods private List<MethodContainer> methodContainer; @ProcessFields private List<FieldContainer> fieldContainer; @AllMethodsWith(AnnotationInElement.class) private List<MethodContainer> methodWithAnnotation; @AllFieldsWith(AnnotationInElement.class) private List<FieldContainer> fieldWithAnnotation;
//METODOS GETS E SETS OMITIDOS public MethodContainer getAnnotedMethod(String name) { for(MethodContainer container : methodContainer){ if(container.getElementName().equals(name)){ return container; } } return null;
} public FieldContainer getAnnotedField(String name){ for(FieldContainer container : fieldContainer){ if(container.getElementName().equals(name)){ return container; } } return null; } }
@ContainerFor(ContainerTarget.FIELDS) public class FieldContainer { @ElementName private String elementName; @ContainsAnnotation(AnnotationInElement.class) private boolean existAnnotation; //METODOS GETS E SETS OMITIDOS }
@ContainerFor(ContainerTarget.METHODS) public class MethodContainer { @ElementName private String elementName; @ContainsAnnotation(AnnotationInElement.class) private boolean existAnnotation;Aqui criaremos uma anotação que tem como alvo os metodos e atributos, pois são eles que estão sendo analizados nesse momento.
//METODOS GETS E SETS OMITIDOS }
@Retention(RUNTIME) @Target({ FIELD, METHOD }) public @interface AnnotationInElement { }
package net.sf.esfinge.metadata.examples.annotationReader.advanced; @AnnotationInClass public class ClassForReader {
private String fieldWinouthAnnotation; @AnnotationInElement private String fieldWithAnnotation; @AnnotationInElement public void methodWithAnnotation(){ }
public void methodWinouthAnnotation(){ } }
public class AnnotationReaderTest { @Test public void testAnnotationsBasic() throws Exception { ContainerClass container; AnnotationReader annotationReader = new AnnotationReader(); container = annotationReader.readingAnnotationsTo(ClassForReader.class, ContainerClass.class); assertEquals(ClassForReader.class, container.getReference()); MethodContainer methodWithAnnotation = container.getAnnotedMethod("methodWithAnnotation"); assertEquals("methodWithAnnotation", methodWithAnnotation.getElementName()); assertTrue(methodWithAnnotation.isExistAnnotation()); MethodContainer methodWinouthAnnotation = container.getAnnotedMethod("methodWinouthAnnotation"); assertEquals("methodWinouthAnnotation", methodWinouthAnnotation.getElementName()); assertFalse(methodWinouthAnnotation.isExistAnnotation()); FieldContainer fieldWinouthAnnotation = container.getAnnotedField("fieldWinouthAnnotation"); assertEquals("fieldWinouthAnnotation", fieldWinouthAnnotation.getElementName()); assertFalse(fieldWinouthAnnotation.isExistAnnotation()); FieldContainer fieldWithAnnotation = container.getAnnotedField("fieldWithAnnotation"); assertEquals("fieldWithAnnotation", fieldWithAnnotation.getElementName()); assertTrue(fieldWithAnnotation.isExistAnnotation()); } }
Anotação | Elemento Alvo | Uso da Anotação |
---|---|---|
@ProcessMethods | Container de Classe | Esta anotação de Fields verifica se existe a anotação selecionada no elemento alvo. Ele contem apenas o método value() que retorana se a anotação selecionada existe, esta anotação deve ser usada em tipos primitivos boolean ou classes do tipo Boolean. |
@ProcessFields | Container de Classe | Esta anotação verifica se existe uma anotação anotada na classe alvo, e alem disso verifica, se esta existe uma propriedade com um nome descrito. |
@AllMethodWith | Container de Classe | Esta anotação pega as informações dos Methods, e coloca em um contêiner com as informações de Methods, deve ser armazenado em List, Set, Map de contêiner com informações sobre os methods. Ele contem apenas o método value() e somente pega os Methods com aquela anotação definida no mesmo. |
@AllFieldsWith | Container de Classe | Esta anotação pega as informações dos fields, e coloca em um contêiner de fields, deve ser armazenado em List, Set, Map de contêineres para os fields.Ele contem apenas o método value() e somente pega os fields com aquela anotação definida no mesmo. |
@ElementProperty | Container de Classe | Pega todos os property da classe e verifica se ele tem alguma anotação. |
São utilizadas para a construção de esquemas personalizados de anotações para a sua aplicação utilizando a estrutura do Esfinge Metadata.
Para eles executarem nescessitam da configAnnotation, que vai uma classe, e do type que vai o ProcessorType
Os Processors são os seguintes, @CustonReader, @ProcessPerMethod, @ProcessPerField.
Os ProcessorType são os seguintes: READER_ADDS_METADATA, READER_IS_PROCESSOR, READER_RETURN_PROCESSOR.
Neste caso o Reader adiciona as informações de processors no container.
Em primeiro Momento criaremos um Container, neste caso vai estar anotado nele o @CustonReader();
@ContainerFor(ContainerTarget.TYPE) @CustomReader(configAnnotation = MyAnnotationReader.class, type = ProcessorType.READER_ADDS_METADATA,
readerConfig = "value") public class Container { private String className; //Get e Set omitidos }
Anotação criada que será executada com o processor.
@Retention(RUNTIME) public @interface MyAnnotationReader { Class<? extends InterfaceWithProcessor> value(); }
Anotação que vai na classe que queremos usar com o Esfinge Metadata com o processor.
@Retention(RUNTIME) @MyAnnotationReader(ExecuteProcessor.class) public @interface MyAnnotation {
}
Processor que vai ser executado, neste caso vai passar o nome do AnnotatedElement que vai no processor para a execução
public class ExecuteProcessor implements InterfaceWithProcessor { @Override public void execute(AnnotatedElement element,Object container) { ((Container)container).setClassName(((Class)element).getName()); } }
@MyAnnotation public class MyClass { }
Metodo main que vai executar o programa, e retornar o nome da classe usando o READER_ADDS_METADATA.
public static void main(String[] args) throws Exception { AnnotationReader reader = new AnnotationReader(); Container ct = new Container(); ct = reader.readingAnnotationsTo(MyClass.class, Container.class); System.out.println(ct.getClassName()); }
@ContainerFor(ContainerTarget.TYPE) public class Container { @ElementName private String elementName; @CustomReader(configAnnotation=PropertyAnnotation.class, type = ProcessorType.READER_IS_PROCESSOR) private List<PropertyProcessorInterface> interf; //gets e sets omitidos }
Anotação que usa a PropertyAnnotation,
Definição da property annotation, ela recebe uma classe que extende uma PropetyProcessorInterface, que você vai definir.@Retention(RUNTIME) @PropertyAnnotation(ExecuteProcessors.class) public @interface OutraAnnotation { }
Interface que foi criada para como um template de como deve ser o PropertyAnnotation.
public interface PropertyProcessorInterface { @ExecuteProcessor public void execute(Annotation ann, AnnotatedElement ael); public void print(); }
Classe que foi anotada na anotação @OutraAnnotation
, está vai executar os metodos que na Interface estão anotados com @ExecuteProcessor
, e ser impresso na tela o que estiver no metodo print()
.
@ContainerFor(ContainerTarget.ALL) public class ExecuteProcessors implements PropertyProcessorInterface { private Annotation ann; private AnnotatedElement ael; @ContainsAnnotation(OutraAnnotation.class) private boolean existAnnotation;
@Override public void execute(Annotation ann, AnnotatedElement ael) { this.ann = ann; this.ael = ael; } public boolean isExistAnnotation() { return existAnnotation; } public void setExistAnnotation(boolean existAnnotation) { this.existAnnotation = existAnnotation; } @Override public void print() { System.out.println("Elemento "+ael+" anotado com " + ann); System.out.println("A anotação "+ ann+ " "+existAnnotation); } }
A classe que foi anotada com a anotação @OutraAnotação
.
@OutraAnnotation public class ClassWithMetadata { }
Aqui vemos o metodo main() de como é executado o Container com os processors, primeiro instancia um novo Container, e logo em seguida,
public static void main(String args[]) throws Exception { AnnotationReader ar = new AnnotationReader(); Container ct = new Container(); ct = ar.readingAnnotationsTo(ClassWithMetadata.class, Container.class); for (PropertyProcessorInterface execute :ct.getInterf()) { execute.print(); } }
Foi criado um contatainer, neste caso foi criado um container que retorna um tipo de container.
public class ContainerReaderReturn { @CustomReader(configAnnotation = AnnotationReturn.class, type= ProcessorType.READER_RETURNS_PROCESSOR,readerConfig="value") private List<ReturnInterface> list; }
Anotação que vai ser configurada no @CustonReader
.
public @interface AnnotationReturn { Class<? extends ReturnProcessorInterface> value(); }
Interface que foi criada para retornar os dados que vão ser retornados pelo container.
public interface ReturnInterface { public String returnDados(); }
@AnnotationReturn
está vai chamar o reader e o reader vai chamar um metodo que vai popular o container.@Retention(RUNTIME) @AnnotationReturn(ProcessorAnnotation.class) public @interface MeuReader { }
ReturnProcessorInterface
, está é a classe que vai executar o processor, e retornar a ReturnInterface.public class ProcessorAnnotation implements ReturnProcessorInterface{ @Override public ReturnInterface toReturn(Annotation an) { ImprimirAnotacao imprimir = new ImprimirAnotacao(an); return (ReturnInterface) imprimir; } }
@MeuReader
, está é que vai ser processada pelo Esfinge Metadata.@MeuReader public class ClassToRead { }
ImprimirAnotação
, está é uma implementação da interface ReturnInterface, neste caso ela vai imprimir a anotação que foi passada para a classe.public class ImprimirAnotacao implements ReturnInterface { private Annotation an; public ImprimirAnotacao(Annotation an) { this.an = an; } @Override public String returnDados() { // TODO Auto-generated method stub return "Anotação "+an.annotationType(); } }
public static void main(String[] args) throws Exception { AnnotationReader ar = new AnnotationReader(); ContainerReaderReturn ct = new ContainerReaderReturn(); ct = ar.readingAnnotationsTo(ClassToRead.class, ct.getClass()); System.out.println(ct.getElementName()); for (ReturnInterface lista : ct.getList()) { System.out.println(lista.returnDados()); } }
Em um primeiro momento criaremos o container com uma interface de processor, esta vai ser definida como:
@ContainerFor(ContainerTarget.TYPE)
public class Container{ @ProcessorPerMethod(configAnnotation=ProcessorAnnotation.class,type=ProcessorType.READER_IS_PROCESSOR) Map<Method,ProcessorInterface> map;
//gets e sets omitidos
}
Criaremos a anotação que foi definida em configAnnotation, está será usada para configurar as anotações que serão usadas para dizer qual vai ser o processador da interface que vai ser utilizado no exemplo.
@Retention(RetentionPolicy.RUNTIME) @SearchInsideAnnotations @SearchOnEnclosingElements @SearchOnAbstractions public @interface ProcessorAnnotation { Class<? extends ProcessorInterface> value(); }
Criaremos a interface ProcessorInterface
, está interface define como será executado o codigo dos processors.
public interface ProcessorInterface { @ExecuteProcessor public void processorInitializationFieldAndAnnotedElement(Annotation ann); public void returnDados(); }
@Entidade
, está está anotada com o @ProcessorAnnotation()
e é passado uma classe que implementa a interface ProcessorInterface
, a anotação @SearchOnAbststractions
esta define que a anotação pode estar nos elementos de nivel de Interface ou de Herança.@Retention(RetentionPolicy.RUNTIME) @SearchOnEnclosingElements @SearchInsideAnnotations @SearchOnAbstractions @ProcessorAnnotation(Processor.class) public @interface Entidade { }
public class Processor implements ProcessorInterface { private String field2; @Override public void processorInitializationFieldAndAnnotedElement(Annotation ann) { field2 = ann.annotationType().getName(); } @Override public void returnDados() { System.out.println(field2); } }
Classe que vai ser passada para a leitura de anotações, esta classe implementa a Interface DominioInterface
, e possui uma implementação do metodo que queremos buscar com o Esfinge Metadata.
public class Dominio implements DominioInterface{ @Override public void entidade1(){} }
Interface criada com a anotação @Entidade
, que por sua vez será buscada no metodo.
public interface DominioInterface {
@Entidade public void entidade1(); }
Execução do EsfingeMetadata, neste caso ele verifica se a entidade está na classe ou na interface do mesmo.
public static void main(String args[]) throws Exception { Container container = new Container(); AnnotationReader a1 = new AnnotationReader(); container = a1.readingAnnotationsTo(Dominio.class, container.getClass()); container.map.forEach((key, value) -> { value.returnDados(); }); }
Exemplo:
Criaremos um container do tipo de field, este por sua vez está anotado com o @ProcessorPerField
.
@ContainerFor(ContainerTarget.TYPE)
public class Container{ @ProcessorPerField(configAnnotation=ProcessorAnnotation.class,type=ProcessorType.READER_IS_PROCESSOR) Map<Field,ProcessorInterface> map;
//gets e sets omitidos
}
Criaremos a anotação que foi definida em configAnnotation
, está será usada para configurar as anotações que serão usadas para dizer qual vai ser o processador da interface que vai ser utilizado no exemplo.
@Retention(RetentionPolicy.RUNTIME) @SearchInsideAnnotations @SearchOnEnclosingElements public @interface ProcessorAnnotation { Class<? extends ProcessorInterface> value(); }
public interface ProcessorInterface { @ExecuteProcessor public void processorInitializationAnnotation(Annotation ann); public void returnDados(); }
@ProcessorAnnotation()
definida como ProcessorField
.@Retention(RetentionPolicy.RUNTIME) @SearchOnEnclosingElements @SearchInsideAnnotations @SearchOnAbstractions @ProcessorAnnotation(ProcessorField.class) public @interface Entidade { String nome(); }
ProcessorField
vai pegar o valor da interface @Entidade
que foi passado como parametro,public class ProcessorField implements ProcessorInterface { private String field1; @Override public void processorInitializationAnnotation(Annotation ann) { field1 =((Entidade)ann).nome(); } @Override public void returnDados() { // TODO Auto-generated method stub System.out.println(field1); } }
@Entidade
neste caso ele vai retornar a propriedade nome()
da @Entidade
.public class Dominio{ @Entidade(nome = "nome 1") private String field1; @Entidade(nome = "nome 2") private int field2; }
@Entidade
na classe Dominio.public static void main(String args[]) throws Exception { Container container = new Container(); AnnotationReader a1 = new AnnotationReader();
container = a1.readingAnnotationsTo(Dominio.class, container.getClass()); container.map.forEach((key, value) -> { value.returnDados(); }); }
Nesta seção falaremos sobre os processors, quais as suas funcionalidades e os atributus nescessarios para a a execução dos processors.
Anotação | Elemento Alvo | Uso da Anotação |
---|---|---|
@ExecuteProcessor | Interface que vai ser executada pelos processors | Dis que o metodo da interface será invocado por qualquer um dos processors, e define qual a ordem dos processors a ser executado por predefinição este valor é zero. |
@ProcessorPerProperty | Container de Classe | Esta anotação processa as das anotações das propriedades, como metodos get ou atributos, quando não sabe-se em qual está sendo utilizado a anotação, passa como parametro o value() que é a interface que está os metodos e o type() que rescebe um dos ProcessorType, tendo como valor padrão ProcessorType.READER_IS_PROCESSOR. |
@ProcessorPerField | Container de Classe | Esta anotação processa somente as anotações dos Atributos da classe, essa anotação tem que ser passada com os parametros value() que é a interface que está os metodos e o type() que rescebe um dos ProcessorType, tendo como valor padrão ProcessorType.READER_IS_PROCESSOR. |
@ProcessorPerMethod | Container de Classe | Esta anotação processa somente as anotações dos Metodos da classe, essa anotação tem que ser passada com os parametros value() que é a interface que está os metodos e o type() que rescebe um dos ProcessorType, tendo como valor padrão ProcessorType.READER_IS_PROCESSOR. |
@CustomReader | Todos os containers | Procura anotações anotadas com a anotação passada como parametro na classe e esta anotação tem que ser passada com os parametros value() que é a interface que está os metodos e o type() que rescebe um dos ProcessorType, tendo como valor padrão ProcessorType.READER_IS_PROCESSOR. |
Reader | Funcionalidade |
---|---|
READER_ADDS_METADATA | Neste caso o reader é o elemento responsável por popular o contêiner demetadados. |
READER_RETURNS_PROCESSOR | O reader retorna um objeto que foi definido como o processor. |
READER_IS_PROCESSOR | O reader é o próprio processor. |
O esquema de anotação da própria API pode ser estendido. Se o framework precisar recuperar um metadados da classe que não é suportada pelas anotações API nativas, novas APIs de leitura de metadados podem ser definidas.
Para criar uma extensão do Metadata Reader é preciso:
Criar uma anotação anotada com
@AnnotationReadingConfig()
este arquivo vai dizer qual anotação está sendo passado;
Colocar a anotação
@ValidFieldType()
, esta anotação diz em que tipos ou classes as anotações serão
anotadas. Exemplo:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @AnnotationReadingConfig(AllFieldsWithReadingProcessor.class) @ValidFieldType({List.class, Set.class,Map.class}) public @interface AllFieldsWith { Class<? extends Annotation> value();
}
Implemente a interface
AnnotationReadingProcessor
, ele tem essa estrutura:
public interface AnnotationReadingProcessor {
void initAnnotation(Annotation an, Field field) throws AnnotationValidationException;
void read(AnnotatedElement classWithMetadata, Object container, ContainerTarget enumStr) throws AnnotationReadingException; }
Onde a função
initAnnotation()
é responsável pela inicialização da classe de validação e o
read()
é responsavel pela leitura das anotações:
Esta parte do framework é usada para validar se os elementos de anotação de codigo e verifica a integridade dos dados se estão compativeis com os parametros passados pelas anotações.
Para executar o validador de metadados deve executar os seguintes passos:
import
org.esfinge.metadata.AnnotationValidationException
, para chamar
as exeções de validação do EsfingeMetadata:
import
org.esfinge.metadata.validate.MetadataValidator
, para declarar
que vai utilizar o EsfingeMetadata para validar o seu codigo:
MetadataValidator.validateMetadataOn()
;
para procurar as anotações de validações do EsfingeMetadata,
onde o Class<?> é a classe que se deseja
validar:
Nesta seção falaremos sobre a validação de propriedades dos metodos, que são passados pelas anotações.
Em um primeiro momento criaremos uma anotação, que tem as
seguintes anotações, @MinValue()
que define o valor minimo que será
passado como parametro da anotação, @MaxValue
, o valor maximo que
será passado como parametro da anotação e o @NotNull
, que define
que a anotação não pode ser NULL
@SearchOnEnclosingElements @SearchInsideAnnotations @Target({ElementType.METHOD, ElementType.TYPE,ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface PointsToUser { @MinValue(value=2) @MaxValue(value=10) int quantity(); @NotNull String name(); }
public class ValidAchevimentsPoints { @PointsToUser(quantity=5,name="ValidPoint") public void setPoints(){ } }Executemos o
MetadataValidator.validateMetadaOn()
para validar as
anotações, neste caso não vai cair em nenhuma exceção de validação.
@Test public void validTestCase() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(ValidAchevimentsPoints.class); }Neste caso declaramos aqui a anotação com os parametros passados para um metodo, vemos aqui que o parametro quantitty é 0 e está abaixo do parametro anotado com a anotação
@MinValue
.public class InvalidAchevimentsPoints { @PointsToUser(quantity=0,name="InvalidPoint") public void setPoints(){ } }
@MinValue
.@Test(expected=AnnotationValidationException.class) public void invalidTestCase() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(InvalidAchevimentsPoints.class); }
Aqui está uma lista de todas as anotações que podem ser usadas para a validação de propriedade.
Anotação | Uso |
---|---|
@Unique | Esta anotação de Fields verifica se existe a anotação selecionada no elemento alvo. Ele contem apenas o método value() que retorana se a anotação selecionada existe, esta anotação deve ser usada em tipos primitivos boolean ou classes do tipo Boolean. |
@NotNull | Esta anotação verifica se existe uma anotação anotada na classe alvo, e alem disso verifica, se esta existe uma propriedade com um nome descrito. |
@MinValue | Esta anotação de field pega o nome do elemento que foi anotado e passa para o field do container. |
@MaxValue | Esta anotação de field pega a classe do elemento que foi anotado e passa para o field que foi anotado com esse elemento. |
@RefersTo | Este validador indica que o valor do atributo de uma anotação deve referir-se (ser igual) ao valor do atributo de uma outra anotação em algum lugar no programa. O validador define os seguintes atributos: annotation(): A anotação alvo ao qual o atributo atual se refere. attributeValue(): O valor do atributo da anotação alvo ao qual o atributo atual se refere. |
Aqui são definidas as anotações de contexto, quais as anotações são permitidas ou proibidas de serem usadas.
Neste caso criaremos uma anotação denominada
@PointsToUserValid() que é a classe que contem as anotações a serem
validadas, eles tem anotada as anotações @NeedToHave
que verifica
se esta anotação contem a anotação passada como parametro, e a
@Prohibits que diz que a anotação não deve ser passada em nenhum
parametro da anotação.
@SearchOnEnclosingElements @SearchInsideAnnotations @Target({ElementType.METHOD, ElementType.TYPE,ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @NeedsToHave(MaxValue.class) @Prohibits(MinValue.class) public @interface PointsToUserValid { @MaxValue(value=10) int quantity(); @NotNull String name(); }
Criaremos uma anotação que tem um metodo que vai ser validado pela anotação criada anteriormente.
public class ValidAchevimentsPoints { @PointsToUserValid(quantity=5,name="ValidPoint") public void setPoints(){ } }
Executemos o MetadataValidator.validateMetadataOn(), neste caso estará executando corretamente.
@Test public void validTestCase() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(PointsToUserValid.class); }
Já neste caso, observamos que tem tambem a anotação
@MinValue, que está proibida com a anotação @Prohibits()
,
@SearchOnEnclosingElements @SearchInsideAnnotations @Target({ElementType.METHOD, ElementType.TYPE,ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @NeedsToHave(MaxValue.class) @Prohibits(MinValue.class) public @interface PointsToUserInvalidNeedToHave { @MinValue(value=2) @MaxValue(value=10) int quantity(); @NotNull String name(); }
Tambem foi criada uma classe com a anotação criada anteriormente.
public class AchevimentsPointsWithNeedToHaveExeption { @PointsToUserInvalidProhibits(quantity=0,name="InvalidPoint") public void setPoints() { } }
Neste caso quando executar o
MetadataValidator.validateMetadataOn() vai cair em uma exceção pois
não está seguindo a regra do @Prohibits
.
@Test(expected=AnnotationValidationException.class) public void needToHaveTestCase() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(AchevimentsPointsWithNeedToHaveExeption.class); }
Aqui está uma lista de todas as anotações que podem ser usadas para a validação de contexto.
Anotação | Uso |
---|---|
@Prohibits | Este validador estabelece que se um elemento de código é anotado com a meta-anotação atual, então, ela proibirá a utilização de outra anotação informada. Este validator é definido por um único atributo value() informando a classe da anotação a ser proibida. |
@NeedsToHave | Este validador é o opostodo @Prohibits.Ele requer que a meta-anotação atual deve ocorrer em elementos que contenham a anotação informada no value(). |
@OneAnnotationWithStaticFieldOnly
, ela vai verificar se o atributo é
estatico.@SearchInsideAnnotations @SearchOnEnclosingElements @StaticFieldOnly @Retention(RetentionPolicy.RUNTIME) public @interface OneAnnotationWithStaticFieldOnly { }
Uma classe com o atributo estatico
name
anotado com @OneAnnotationWithStaticFieldOnly
public class StaticFieldValid { @OneAnnotationWithStaticFieldOnly private static String name; }Criaremos aqui um caso de teste valido, pois o atributo anotado criado anteriormente, é estatico.
@Test public void testValidModyfers() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(StaticFieldValid.class); }
Aqui estamos vendo um caso com erro, pois o elemento anotado com a anotação @OneAnnotationWithStaticFieldOnly
, não é um atributo estatico e portanto vai ocasionar uma exeção.
public class StaticFieldInvalid{ @OneAnnotationWithStaticFieldOnly protected String nameInvalid; }
Criaremos aqui um caso de teste que vai retornar a exceção AnnnotationValidationExeption, pois possui um atributo invalido na classe StaticFieldInvalid
.
@Test(expected=AnnotationValidationException.class) public void testInvalidModyfers() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(StaticFieldInvalid.class); }
Anotação | Uso |
---|---|
@FinalFieldOnly | Esta meta-anotação indica que o campo que utiliza a anotação anotada precisa ter o modificador final. |
@StaticFieldOnly | Esta meta-anotação indica que o campo que utiliza a anotação anotada precisa ter o modificador static. |
@TransientFieldOnly | Esta meta-anotação indica que o campo que utiliza a anotação anotada precisa ter o modificador transient. |
@VolatileFieldOnly | Esta anotação de field pega a classe do elemento que foi anotado e passa para o field que foi anotado com esse elemento. |
@InstanceFieldOnly | Esta meta-anotação indica que o campo que utiliza a anotação anotada NÃO pode ter o modificador static. |
Nesta seção falaremos sobre as validações de visibilidade, e verificamos se os atributos são visiveis ou proibidos no codigo.
Para esse exemplo criaremos uma anotação chamada @OneAnnotationWithFieldVisibilityRequired
, ela contem a seguinte anotação do EsfingeMetadata. @FieldVisibilityRequired
, que tem como parametro a visibilidade do atributo, neste caso o atributo deve ser privado.
@SearchInsideAnnotations @SearchOnEnclosingElements @FieldVisibilityRequired(itNeedsToHaveThisVisibility = "private") @Retention(RetentionPolicy.RUNTIME) public @interface OneAnnotationWithFieldVisibilityRequired { }
Criaremos uma classe e anotamos o atributo name, com a anotação criada anteriormente, e vemos que o tipo do atributo é o mesmo da que foi passado na anotação criada anteriormente.
public class VisibilityRequeridValid { @OneAnnotationWithFieldVisibilityRequired private String name; }
Executamos um caso de teste, para verificar se o EsfingeMetadata valida corretamente esse parametro.
@Test public void testValidVisibility() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(VisibilityRequeridValid.class); }
Neste caso temos um elemento com visibilidade protected, este difere da visibilidade requirida na validação.
public class VisibilityRequeridInvalid { @OneAnnotationWithFieldVisibilityRequired protected String name; }
Aqui está o exemplo de validação com anotação invalida, neste caso está esperado a exceção AnnotationValidationExcepition
, pois neste caso está esperando um atributo com a visibilidade private mas está recebendo um atributo com a visibilidade protected.
@Test(expected=AnnotationValidationException.class) public void testInvalidVisibility() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(VisibilityRequeridInvalid.class); }
Aqui está a listas de anotações que podem ser utilizados para validação de visibilidade.
Anotação | Uso |
---|---|
@FieldVisibilityForbidden | Esta meta-anotação indica que o campo que utiliza a anotação anotada NÃO pode ter a visibilidade passada no parâmetro itCannotHaveThisVisibility. |
@FieldVisibilityRequired | Esta meta-anotação indica que o campo que utiliza a anotação anotada precisa ter a visibilidade passada no parâmetro itNeedsToHaveThisVisibility. |
Aqui falaremos sobre validadores de tipo para atributos, nesta seção tem somente a anotação @ValidFieldTypes
.
Criaremos uma anotação anotada com @ValidFIeldType
, que recebe como parametro listValidTypes que é um array de classes, este define quais tipos são permitidos de existir no atributo, neste caso é String, List.
@SearchInsideAnnotations @SearchOnEnclosingElements @ValidFieldTypes(listValidTypes = {String.class, List.class}) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface OneAnnotationWithValidFieldTypes { }
Aqui criamos uma classe com o atributo a ser validado, neste caso vai ser validado pois o tipo que está sendo passado é do mesmo tipo que foi passado previamente para a anotação @OneAnnotationWithValidFieldTypes
.
public class TypeFieldValid { @OneAnnotationWithValidFieldTypes private String name; }
Neste caso vai ocorrer uma exceção pois o atributo não passa nenhum dos tipos definido na anotaçaõ @OneAnnotationWithValidFieldTypes
.
public class TypeFieldInvalid{ @OneAnnotationWithValidFieldTypes private int invalid; }
Aqui tem um exemplo com os dois comportamentos que podem ocorrer na execução dessa validação, o validTypeExamples() ocorre quando não tem nenhum erro de validação de propriedades, enquanto o invalidTypeExamples() vai disparar a exeção que esta sendo experada.
public class TypeExamples { @Test public void validTypeExamples() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(TypeFieldValid.class); }
@Test(expected=AnnotationValidationException.class) public void invalidTypeExamples() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(TypeFieldInvalid.class); } }
Anotação | Uso |
---|---|
@ValidFieldTypes | Esta meta-anotação indica que o campo que utiliza a anotação anotada precisa ter um tipo correspondente ao passado no parâmetro listValidTypes. |
Nesta seção vai ser falado sobre a validação de contexto por metodos, e quais são as anotações que foram criadas para o uso deles, alem de um exemplo basico de uso.
Em um primeiro momento criaremos uma anotação chamada @OneAnnotationWithFinalMethodOnly
, que contem a anotação @FinalMethodOnly
que vai ser responsavel por dizer que o metodo que essa classe está anotada é final.
@FinalMethodOnly @Retention(RetentionPolicy.RUNTIME) public @interface OneAnnotationWithFinalMethodOnly { }
Exemplo de um metodo anotado com a anotação criada anteriormente, este é a versão correta de dizer que um metodo é valido, conforme o parametro criado anteriormente.
public class FinalMethodOnlyValid { @OneAnnotationWithFinalMethodOnly public final void finalValid(){ } }
Exemplo de um metodo anotado com a anotação criada anteriormente, este é invalido, pois o metodo anotado não é do tipo final.
import net.sf.esfinge.metadata.examples.validate.method.test.OneAnnotationWithFinalMethodOnly; public class FinalMethodOnlyInvalid {
@OneAnnotationWithFinalMethodOnly public void finalInvalid(){} }
Exemplo de caso de teste para validação dos metodos, o caso modifersValid() contem a anotação valida, enquanto o modifersInvalid() contem a anotação que retorna a exceção requirida.
public class ModifersTest @Test public void modifersValid() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(FinalMethodOnlyValid.class); } @Test(expected=AnnotationValidationException.class) public void modifersInvalid() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(FinalMethodOnlyInvalid.class); } }
Aqui a lista de anotações que podem ser usados para validação por modificadores de Metodos.
Anotação | Uso |
---|---|
@FinalMethodOnly | Esta meta-anotação indica que o método que utiliza a anotação anotada precisa ter o modificador final. |
@StaticMethodOnly | Esta meta-anotação indica que o método que utiliza a anotação anotada precisa ter o modificador static. |
@SyncronizedMethodOnly | Esta meta-anotação indica que o método que utiliza a anotação anotada precisa ter o modificador synchronized. |
@StrictfpMethodOnly | Esta meta-anotação indica que o método que utiliza a anotação anotada precisa ter o modificador strictfp. |
@InstanceMethodOnly | Esta meta-anotação indica que o método que utiliza a anotação anotada NÃO pode ter o modificador static. |
Exemplo de @MethodVisibilityForbidden
, neste caso dis qual o tipo que o metodo não pode ser, neste caso o metodo não pode ser privado.
@MethodVisibilityForbidden(itCannotHaveThisVisibility = "private") @Retention(RetentionPolicy.RUNTIME) public @interface OneAnnotationWithMethodVisibilityForbidden { }
Aqui um exemplo valido, repare que neste caso o metodo não é privado.
public class VisibilityForbidenValid { @OneAnnotationWithMethodVisibilityForbidden public double someCalc(double a, double b){ return Math.pow(( a * b ) * 4.2, 5); } }
Exemplo que pode ocorrer uma exceção, o metodo é privado.
public class VisibilityForbidenInvalid { @OneAnnotationWithMethodVisibilityForbidden private double someCalc(double a, double b){ return Math.pow(( a * b ) * 4.2, 5); } }
Aqui está um caso de teste que faz a verificação das classes criadas para a validação, repare que o testVisibilityValid()
valida a anotação, enquanto o testVisibilityInvalid()
retorna uma anotação que contem exceção apresentada.
public class ValidateVisibility { @Test public void testVisibilityValid() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(VisibilityForbidenValid.class); }
@Test(expected=AnnotationValidationException.class) public void testVisibilityInvalid() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(VisibilityForbidenInvalid.class); } }
Anotação | Uso |
---|---|
@MethodVisibilityForbidden | Esta meta-anotação indica que o método que utiliza a anotação anotada NÃO pode ter a visibilidade passada no parâmetro itCannotHaveThisVisibility. |
@MethodVisibilityRequired | Esta meta-anotação indica que o método que utiliza a anotação anotada precisa ter a visibilidade passada no parâmetro itNeedsToHaveThisVisibility. |
@MethodNamingConvention
, esta recebe como parametro um REGEX com o nome do metodo.@MethodNamingConvention(regexNamingConvencion = "^get") @Retention(RetentionPolicy.RUNTIME) public @interface OneAnnotationWithMethodNamingConvention { }
Criaremos uma classe valida usando a anotação criada anteriormente, neste caso vai executar com sucesso pois o metodo começa com o REGEX ^get.
public class NamingConvectionTrue { @OneAnnotationWithMethodNamingConvention public void getName(){ } }
Neste caso, não vai estar correto pois o nome do metodo não contem o REGEX ^get.
public class NamingConvectionFalse { @OneAnnotationWithMethodNamingConvention public void setName(){ } }Aqui está o codigo de teste, ele vai retornar as exeções de codigo para o caso que não satisfaz a validação, e não vai retornar nada no codigo que satisfaz a validação.
public class NamingConvectionTest { @Test public void NamingConvectionValid() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(NamingConvectionTrue.class); } @Test(expected=AnnotationValidationException.class) public void NamingConvectionInvalid() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(NamingConvectionFalse.class); } }
Anotação | Uso |
---|---|
@MethodNammingConvention | Esta meta-anotação indica que o método que utiliza a anotação anotada precisa ter um nome de método de acordo com a expressão regular passada no parâmetro regexNamingConvencion. |
Em um primeiro momento criaremos uma anotação que tem anotada o @ValidNumbersOfParameters
, neste caso o valor é 2.
@ValidNumberOfParameters(numberOfParameters = 2) @Retention(RetentionPolicy.RUNTIME) public @interface OneAnnotationWithValidNumberOfParameters { }
Criaremos uma classe com os numeros parametros validos pela anotação criada anteriormente.
public class ParameterForbidenValid { @OneAnnotationWithValidNumberOfParameters public double someCalc(double a, double b){ return Math.pow(( a * b ) * 4.2, 5); } }
Aqui vai ser criada uma classe com os parametros invalidos.
public class ParameterForbidenInvalid { @OneAnnotationWithValidNumberOfParameters private strictfp double someCalc(double a){ return Math.pow(( a ) * 4.2, 5); } }
Criaremos o caso de teste para a verificação do uso com anotação valida e anotação invalida.
public class ParameterVisibility { @Test public void testVisibilityValid() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(ParameterForbidenValid.class); } @Test(expected=AnnotationValidationException.class) public void testVisibilityInvalid() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(ParameterForbidenInvalid.class); } }
Listas de anotação de validação por parametros.
Anotação | Uso |
---|---|
@NoParameterMethodOnly | Esta meta-anotação indica que o método que utiliza a anotação anotada NÃO pode ter parâmetros. |
@ValidNunberOfParameters | Esta meta-anotação indica que o método que utiliza a anotação anotada precisa ter a quantidade de parâmetros passado no parâmetro numberOfParameters. |
@ValidMethodParameterTypes | Esta meta-anotação indica que o método que utiliza a anotação anotada precisa ter os parâmetros correspondentes ao que foi passado no parâmetro validParameters, onde os parâmetros válidos são indicados pela anotação @Parameters no parâmetro parameters. |
@ValidMethodReturn
, que é responsavel por dizer qual o retorno esperado para determinado metodo, neste caso do exemplo é int.class ou Integer.class.@ValidMethodReturn(validTypesToReturn = {int.class, Integer.class}) @Retention(RetentionPolicy.RUNTIME) public @interface OneAnnotationWithValidMethodReturn { }
Criaremos uma classe com um metodo anotado com a anotação criada anteriormente, neste caso é valido pois está retornando um tipo inteiro.
public class ReturnValid { @OneAnnotationWithValidMethodReturn public int someCalc(int a, int b){ return a+b; } }
Criaremos uma classe com um metodo anotado com a anotação criada anteriormente, neste caso é invalido pois está retornando uma string.
public class ReturnInvalid { @OneAnnotationWithValidMethodReturn private String someCalc(double a){ return "INVALID"; } }
Aqui a execução do caso de teste, repare que quando se testa o caso invalido ele cai em uma exceção de AnnotationValidationException
.
public class ParameterVisibility { @Test public void testVisibilityValid() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(ReturnValid.class); } @Test(expected=AnnotationValidationException.class) public void testVisibilityInvalid() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(ReturnInvalid.class); } }
Anotação | Uso |
---|---|
@ForbiddenMethodReturn | Esta meta-anotação indica que o método que utiliza a anotação anotada NÃO pode ter o retorno passado no parâmetro invalidTypesToReturn. |
@ValidMethodReturn | Esta meta-anotação indica que o método que utiliza a anotação anotada precisa ter o retorno passado no parâmetro validTypesToReturn. |
Para criar uma extensão para o validador de metadados é necessário:
Criar uma anotação anotada com:
@ToValidateProperty()
Exemplo:
@ToValidateProperty(validationClass = UniqueAnnotationValidator.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Unique { }
Implemente o AnnotationPropertyValidator
, ele tem essa
estrutura:
public interface AnnotationPropertyValidator { public void initialize(Annotation self); public void validate(Annotation annotationOnElement, AnnotatedElement annotatedWithMainAnnotation, Method annotatedWithValidation, Object annotationPropertyValue) throws AnnotationValidationException; }
Onde o método
initialize()
é responsável pela inicialização da classe de validação e o
validate()
é responsavel pela validação;
Segue um exemplo da implementação da Anotação Unique.
public class UniqueAnnotationValidator implements AnnotationPropertyValidator { private List<Annotation> annotationList = new ArrayList<Annotation>(); @Override public void initialize(Annotation self) { } @Override public void validate(Annotation annotationOnElement, AnnotatedElement annotatedWithMainAnnotation, Method annotatedWithValidation, Object annotationPropertyValue) throws AnnotationValidationException { Method[] methods = annotationOnElement.annotationType().getMethods(); for (Method m : methods) { for(Annotation an : m.getAnnotations()){ if(an instanceof Unique){ annotationList.add(an); } } } if(annotationList.size() > 1){ throw new AnnotationValidationException("The @"+annotationOnElement.annotationType().getSimpleName()+" must have only one field annotated with @Unique!"); }else if (annotationList.size() == 0 ) { throw new AnnotationValidationException("The annotation @" + Unique.class.getSimpleName().toString() + " was not found"); } } }
Aqui criaremos um exemplo de uso desta anotação, em um primeiro momento criaremos uma anotação, esta anotação possue dois atributos anotado com @Unique
, sendo que neste caso somente deve ter uma anotação anotada com @Unique.
@SearchOnEnclosingElements @SearchInsideAnnotations @Target({ElementType.METHOD,ElementType.TYPE, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface PointsToUser { @Unique int quantity(); @Unique String name(); }Aqui foi criado um caso de teste que vai retornar a
AnnotationValidationExeption
, pois a anotação a ser validada é invalida de acordo com o funcionamento da anotação @Unique
.@Test(expected = AnnotationValidationException.class) public void VerifiyIsUnique() throws AnnotationValidationException, IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, SecurityException { MetadataValidator.validateMetadataOn(Classe.class); }
Tambem no Metadata Validator possui outro ponto para a extenção do Metadata Validator a Interface AnnotationValidator.
Aqui um exemplo de uso desta anotação.
Em primeiro momento criaremos uma anotação anotada com@ToValidate()
, esta tem como parametro a implementação da interface AnnotationValidator
.@ToValidate(value = ProhibitsAnnotationValidator.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Prohibits { public Class<? extends Annotation> value(); }
Em seguida criaremos o a implementação do validador.
public class ProhibitsAnnotationValidator implements AnnotationValidator{ protected Class<? extends Annotation> whatItNeedsToProhibits; @Override public void initialize(Annotation self) { Prohibits ntp = (Prohibits) self; whatItNeedsToProhibits = ntp.value(); } @Override public void validate(Annotation toValidate, AnnotatedElement annotated) throws AnnotationValidationException { if(AnnotationFinder.findAnnotation(annotated, whatItNeedsToProhibits).size() > 0){ throw new AnnotationValidationException("The annotation it needs to prohibits was found = "
+ whatItNeedsToProhibits.toString()); } } }
Criamos então duas anotações que implementa a anotação @Prohibits
, esta recebe uma anotação que [não deve ser colocada de nenhuma maneira no projeto, cada uma proibe a outra.
@Prohibits(OptimizeExecution01.class) @Retention(RetentionPolicy.RUNTIME) public @interface Logging01 { }
@SearchOnEnclosingElements @SearchInsideAnnotations @Prohibits(Logging01.class) @Retention(RetentionPolicy.RUNTIME) public @interface OptimizeExecution01 { }
Em seguida criaremos o teste das classes, vai acionar a AnnotationValidationExeption pois esta possui as anotações proibidas de executar.
public class OrderProcessing02 { @Logging01 @OptimizeExecution01 public void registerPurchase(Purchase p) { } } @Test(expected = AnnotationValidationException.class) public void CTWithExeption() throws AnnotationValidationException { MetadataValidator.validateMetadataOn(OrderProcessing02.class); }
Apoio