/** * * Copyright 2009-2011 Rickard Öberg AB * * 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 org.qi4j.library.rest.server.restlet; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.qi4j.api.composite.TransientComposite; import org.qi4j.api.constraint.Constraint; import org.qi4j.api.constraint.ConstraintDeclaration; import org.qi4j.api.constraint.Constraints; import org.qi4j.api.injection.scope.Structure; import org.qi4j.api.object.NoSuchObjectException; import org.qi4j.api.structure.Module; import org.qi4j.library.rest.server.api.ObjectSelection; import org.qi4j.library.rest.server.api.constraint.InteractionConstraint; import org.qi4j.library.rest.server.api.constraint.InteractionConstraintDeclaration; import org.qi4j.library.rest.server.api.constraint.InteractionValidation; import org.qi4j.library.rest.server.api.constraint.RequiresValid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * JAVADOC */ public class InteractionConstraintsService implements InteractionConstraints { @Structure Module module; Logger logger = LoggerFactory.getLogger( InteractionConstraintsService.class ); private Map<Method, InteractionConstraintsBinding> methodsConstraints = new ConcurrentHashMap<Method, InteractionConstraintsBinding>(); private Map<Class, InteractionConstraintsBinding> classConstraints = new ConcurrentHashMap<Class, InteractionConstraintsBinding>(); @Override public boolean isValid( Method method, ObjectSelection objectSelection, Module module ) { return getConstraints( method, module ).isValid( objectSelection ); } @Override public boolean isValid( Class resourceClass, ObjectSelection objectSelection, Module module ) { return getConstraints( resourceClass, module ).isValid( objectSelection ); } private InteractionConstraintsBinding getConstraints( Method method, Module module ) { InteractionConstraintsBinding constraintBindings = methodsConstraints.get( method ); if( constraintBindings == null ) { constraintBindings = findConstraints( method, module ); methodsConstraints.put( method, constraintBindings ); } return constraintBindings; } private InteractionConstraintsBinding getConstraints( Class aClass, Module module ) { InteractionConstraintsBinding constraintBindings = classConstraints.get( aClass ); if( constraintBindings == null ) { constraintBindings = findConstraints( aClass, module ); classConstraints.put( aClass, constraintBindings ); } return constraintBindings; } private InteractionConstraintsBinding findConstraints( Method method, Module module ) { List<Binding> methodConstraintBindings = new ArrayList<Binding>(); for( Annotation annotation : method.getAnnotations() ) { if( annotation.annotationType().equals( RequiresValid.class ) ) { RequiresValid requiresValid = (RequiresValid) annotation; Class contextClass = method.getDeclaringClass(); if( InteractionValidation.class.isAssignableFrom( contextClass ) ) { InteractionValidation validation = null; if( TransientComposite.class.isAssignableFrom( contextClass ) ) { validation = (InteractionValidation) module.newTransient( contextClass ); } else { validation = (InteractionValidation) module.newObject( contextClass ); } methodConstraintBindings.add( new RequiresValidBinding( requiresValid, validation ) ); } } else if( annotation.annotationType().getAnnotation( ConstraintDeclaration.class ) != null ) { Constraints constraints = annotation.annotationType().getAnnotation( Constraints.class ); for( Class<? extends Constraint<?, ?>> aClass : constraints.value() ) { try { Constraint<Annotation, Object> constraint = (Constraint<Annotation, Object>) aClass.newInstance(); Class roleClass = (Class) ( (ParameterizedType) aClass.getGenericInterfaces()[ 0 ] ).getActualTypeArguments()[ 1 ]; ConstraintBinding constraintBinding = new ConstraintBinding( constraint, annotation, roleClass ); methodConstraintBindings.add( constraintBinding ); } catch( InstantiationException e ) { e.printStackTrace(); } catch( IllegalAccessException e ) { e.printStackTrace(); } } } else if( annotation.annotationType().getAnnotation( InteractionConstraintDeclaration.class ) != null ) { Class<? extends InteractionConstraint> constraintClass = annotation.annotationType() .getAnnotation( InteractionConstraintDeclaration.class ) .value(); InteractionConstraint<Annotation> constraint = null; try { try { constraint = module.newObject( constraintClass ); } catch( NoSuchObjectException e ) { constraint = constraintClass.newInstance(); } } catch( Exception e ) { continue; // Skip this constraint } InteractionConstraintBinding constraintBinding = new InteractionConstraintBinding( constraint, annotation ); methodConstraintBindings.add( constraintBinding ); } } if( methodConstraintBindings.isEmpty() ) { methodConstraintBindings = null; } return new InteractionConstraintsBinding( methodConstraintBindings ); } private InteractionConstraintsBinding findConstraints( Class aClass, Module module ) { List<Binding> classConstraintBindings = new ArrayList<Binding>(); for( Annotation annotation : aClass.getAnnotations() ) { if( annotation.annotationType().getAnnotation( ConstraintDeclaration.class ) != null ) { Constraints constraints = annotation.annotationType().getAnnotation( Constraints.class ); for( Class<? extends Constraint<?, ?>> constraintClass : constraints.value() ) { try { Constraint<Annotation, Object> constraint = (Constraint<Annotation, Object>) constraintClass.newInstance(); Class roleClass = (Class) ( (ParameterizedType) constraint.getClass() .getGenericInterfaces()[ 0 ] ).getActualTypeArguments()[ 1 ]; ConstraintBinding constraintBinding = new ConstraintBinding( constraint, annotation, roleClass ); classConstraintBindings.add( constraintBinding ); } catch( InstantiationException e ) { e.printStackTrace(); } catch( IllegalAccessException e ) { e.printStackTrace(); } } } else if( annotation.annotationType().getAnnotation( InteractionConstraintDeclaration.class ) != null ) { Class<? extends InteractionConstraint> constraintClass = annotation.annotationType() .getAnnotation( InteractionConstraintDeclaration.class ) .value(); InteractionConstraint<Annotation> constraint = null; try { try { constraint = module.newObject( constraintClass ); } catch( NoSuchObjectException e ) { constraint = constraintClass.newInstance(); } } catch( Exception e ) { continue; // Skip this constraint } InteractionConstraintBinding constraintBinding = new InteractionConstraintBinding( constraint, annotation ); classConstraintBindings.add( constraintBinding ); } } if( classConstraintBindings.isEmpty() ) { classConstraintBindings = null; } return new InteractionConstraintsBinding( classConstraintBindings ); } interface Binding { boolean isValid( ObjectSelection objectSelection ); } public static class InteractionConstraintsBinding { List<Binding> bindings; public InteractionConstraintsBinding( List<Binding> bindings ) { this.bindings = bindings; } public boolean isValid( ObjectSelection objectSelection ) { if( bindings != null ) { for( Binding constraintBinding : bindings ) { if( !constraintBinding.isValid( objectSelection ) ) { return false; } } } return true; } } public class RequiresValidBinding implements Binding { RequiresValid annotation; private final InteractionValidation validation; public RequiresValidBinding( RequiresValid annotation, InteractionValidation validation ) { this.validation = validation; this.annotation = annotation; } @Override public boolean isValid( ObjectSelection objectSelection ) { try { return validation.isValid( annotation.value() ); } catch( IllegalArgumentException e ) { return false; } catch( Throwable e ) { logger.warn( "Could not check validation constraint for '" + annotation.value() + "'", e ); return false; } } } public class ConstraintBinding implements Binding { Constraint<Annotation, Object> constraint; Annotation annotation; Class roleClass; public ConstraintBinding( Constraint<Annotation, Object> constraint, Annotation annotation, Class roleClass ) { this.constraint = constraint; this.annotation = annotation; this.roleClass = roleClass; } @Override public boolean isValid( ObjectSelection objectSelection ) { try { Object checkedObject = roleClass.equals( ObjectSelection.class ) ? objectSelection : objectSelection.get( roleClass ); return constraint.isValid( annotation, checkedObject ); } catch( IllegalArgumentException e ) { return false; } catch( Throwable e ) { logger.warn( "Could not check constraint " + constraint.getClass().getName(), e ); return false; } } } public class InteractionConstraintBinding implements Binding { InteractionConstraint<Annotation> constraint; Annotation annotation; public InteractionConstraintBinding( InteractionConstraint<Annotation> constraint, Annotation annotation ) { this.constraint = constraint; this.annotation = annotation; } @Override public boolean isValid( ObjectSelection objectSelection ) { try { return constraint.isValid( annotation, objectSelection ); } catch( Throwable e ) { logger.warn( "Could not check constraint " + constraint.getClass().getName(), e ); return false; } } } }