/*
* Copyright (c) 2013-2015 the original author or authors
*
* Licensed 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 io.werval.runtime.filters;
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import io.werval.api.Application;
import io.werval.api.Global;
import io.werval.api.context.Context;
import io.werval.api.filters.Filter;
import io.werval.api.filters.FilterChain;
import io.werval.api.filters.FilterWith;
import io.werval.runtime.exceptions.WervalRuntimeException;
import io.werval.runtime.filters.FilterChainInstance.FilterChainControllerTail;
import io.werval.util.Couple;
/**
* FilterChain Factory.
*/
public final class FilterChainFactory
{
public FilterChain buildFilterChain( Application app, Global global, Context context )
{
Queue<Couple<Class<? extends Filter>, Annotation>> filters = new ArrayDeque<>();
filters.addAll( findFiltersOnType( global.getClass() ) );
filters.addAll( findFiltersOnType( context.route().controllerType() ) );
filters.addAll( findFilters( context.route().controllerMethod().getAnnotations() ) );
return buildFilterChain( app, global, filters, context );
}
private List<Couple<Class<? extends Filter>, Annotation>> findFiltersOnType( Class<?> type )
{
List<Couple<Class<? extends Filter>, Annotation>> filters = new ArrayList<>();
if( type.getSuperclass() != null )
{
filters.addAll( findFiltersOnType( type.getSuperclass() ) );
}
if( type.getInterfaces() != null )
{
for( Class<?> clazz : type.getInterfaces() )
{
filters.addAll( findFiltersOnType( clazz ) );
}
}
filters.addAll( findFilters( type.getAnnotations() ) );
return filters;
}
private List<Couple<Class<? extends Filter>, Annotation>> findFilters( Annotation... annotations )
{
List<Couple<Class<? extends Filter>, Annotation>> filters = new ArrayList<>();
for( Annotation annotation : annotations )
{
if( annotation.annotationType().getName().startsWith( "java.lang" ) )
{
// Skip java.lang annotations, there contains cyclic declarations causing stack overflows
}
else if( FilterWith.class.equals( annotation.annotationType() ) )
{
// Direct @FilterWith usage
FilterWith filterWith = (FilterWith) annotation;
for( Class<? extends Filter> filterClass : filterWith.value() )
{
filters.add( Couple.leftOnly( filterClass ) );
}
}
else
{
// Repeatable annotation usage
Method[] methods = annotation.annotationType().getDeclaredMethods();
if( methods.length == 1
&& "value".equals( methods[0].getName() )
&& methods[0].getReturnType().isArray() )
{
try
{
Object[] array = (Object[]) methods[0].invoke( annotation );
List<Annotation> repeatedAnnotations = new ArrayList<>();
for( Object inner : array )
{
try
{
Annotation innerAnnotation = (Annotation) inner;
if( innerAnnotation.annotationType().getAnnotation( Repeatable.class ) != null
&& annotation.annotationType().isAssignableFrom(
innerAnnotation.annotationType().getAnnotation( Repeatable.class ).value()
) )
{
repeatedAnnotations.add( innerAnnotation );
}
}
catch( ClassCastException ex )
{
// Ignored
}
}
if( repeatedAnnotations.isEmpty() )
{
// Not a Repeatable annotation usage, handling as any other annotation
for( Annotation innerAnnotation : annotation.annotationType().getAnnotations() )
{
if( FilterWith.class.equals( innerAnnotation.annotationType() ) )
{
FilterWith filterWith = (FilterWith) innerAnnotation;
for( Class<? extends Filter> filterClass : filterWith.value() )
{
filters.add( Couple.of( filterClass, annotation ) );
}
}
else
{
filters.addAll( findFilters( innerAnnotation ) );
}
}
}
else
{
// This is a Repeatable annotation usage !
filters.addAll(
findFilters(
repeatedAnnotations.toArray( new Annotation[ repeatedAnnotations.size() ] )
)
);
}
}
catch( IllegalAccessException | IllegalArgumentException | InvocationTargetException ex )
{
throw new WervalRuntimeException( ex );
}
}
else
{
// Any other annotation
for( Annotation innerAnnotation : annotation.annotationType().getAnnotations() )
{
if( FilterWith.class.equals( innerAnnotation.annotationType() ) )
{
FilterWith filterWith = (FilterWith) innerAnnotation;
for( Class<? extends Filter> filterClass : filterWith.value() )
{
filters.add( Couple.of( filterClass, annotation ) );
}
}
else
{
filters.addAll( findFilters( innerAnnotation ) );
}
}
}
}
}
return filters;
}
private FilterChain buildFilterChain(
Application app, Global global, Queue<Couple<Class<? extends Filter>, Annotation>> filters, Context context
)
{
if( filters.isEmpty() )
{
return new FilterChainControllerTail( app, global );
}
return new FilterChainInstance(
app,
global,
filters.poll(),
buildFilterChain( app, global, filters, context )
);
}
}