/******************************************************************************* * Copyright (c) 2014, 2015 IBH SYSTEMS GmbH. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBH SYSTEMS GmbH - initial API and implementation *******************************************************************************/ package org.eclipse.packagedrone.web.controller.binding; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.eclipse.packagedrone.utils.converter.ConverterManager; import org.eclipse.packagedrone.web.ModelAndView; import org.eclipse.packagedrone.web.controller.validator.ValidationResult; import org.eclipse.packagedrone.web.controller.validator.Validator; public class BindingManager { public static class Result extends SimpleBindingResult { } public interface Call { public Object invoke () throws Exception; } private final ConverterManager converter; public BindingManager () { this.converter = ConverterManager.create (); } /** * Create a new BindingManager with default binders * <p> * This call creates a new BindingManager instance and add the following * binders: * </p> * <ul> * <li>{@link BindingManager}</li> * <li>{@link BindingManagerBinder}</li> * </ul> * * @param data * the initial data for the MapBinder * @return * a new binder manager instance */ public static final BindingManager create ( final Map<String, Object> data ) { final BindingManager result = new BindingManager (); result.addBinder ( new MapBinder ( data ) ); result.addBinder ( new BindingManagerBinder () ); return result; } protected Binding bindValue ( final BindTarget target, final ConverterManager converter ) { for ( final Binder binder : this.binders ) { final Binding binding = binder.performBind ( target, converter, this ); if ( binding != null ) { return binding; } } return null; } private BindTarget createParameterTarget ( final Parameter parameter, final Object[] args, final int argumentIndex ) { return new ParameterBindTarget ( parameter, args, argumentIndex ); } protected BindTarget createPropertyTarget ( final Object object, final PropertyDescriptor pd ) { return new PropertyBindTarget ( object, pd ); } public Call bind ( final Method method, final Object targetObject ) { final Parameter[] p = method.getParameters (); final Binding[] bindings = new Binding[p.length]; final Object[] args = new Object[p.length]; for ( int i = 0; i < p.length; i++ ) { final BindTarget target = createParameterTarget ( p[i], args, i ); final Binding binding = bindValue ( target, this.converter ); if ( binding != null ) { bindings[i] = binding; target.bind ( binding ); mergeErrors ( binding.getBindingResult (), this.result ); } else { throw new IllegalStateException ( String.format ( "Unable to bind parameter '%s' (%s)", p[i].getName (), p[i].getType () ) ); } } return new Call () { @Override public Object invoke () throws Exception { Object result = method.invoke ( targetObject, args ); for ( final Binding binding : bindings ) { result = binding.postProcess ( result ); } result = postProcess ( result ); return result; } }; } /** * Merge all errors of this binding into this result * * @param binding * the binding to merge * @param result * the result to merge into */ private static void mergeErrors ( final BindingResult bindingResult, final BindingResult result ) { if ( bindingResult == null ) { return; } result.addErrors ( bindingResult.getLocalErrors () ); for ( final Map.Entry<String, BindingResult> child : bindingResult.getChildren ().entrySet () ) { mergeErrors ( child.getValue (), result.getChildOrAdd ( child.getKey () ) ); } } protected Object postProcess ( final Object result ) { if ( result instanceof ModelAndView ) { ( (ModelAndView)result ).put ( BindingResult.ATTRIBUTE_NAME, this.result ); } else if ( result instanceof String ) { final ModelAndView mav = new ModelAndView ( (String)result ); mav.put ( BindingResult.ATTRIBUTE_NAME, this.result ); return mav; } return result; } private final Collection<Binder> binders = new LinkedList<> (); private Validator validator; private final Result result = new Result (); public void addBinder ( final Binder binder, final boolean initializeBinder ) { if ( initializeBinder ) { initializeBinder ( binder ); } this.binders.add ( binder ); } public void addBinder ( final Binder binder ) { addBinder ( binder, true ); } /** * Initialize the binder with our current state * * @param binder * the binder to initialize * @return the list of exceptions or <code>null</code> if there were none */ private void initializeBinder ( final Binder binder ) { for ( final Method m : binder.getClass ().getMethods () ) { if ( !m.isAnnotationPresent ( Binder.Initializer.class ) ) { continue; } final Call call = bind ( m, binder ); try { call.invoke (); } catch ( final Exception e ) { throw new RuntimeException ( String.format ( "Failed to initialze binder: %s # %s", binder, m ), e ); } } } public void bindProperties ( final Object o ) throws Exception { if ( o == null ) { return; } final Class<?> objectClass = o.getClass (); final BeanInfo bi = Introspector.getBeanInfo ( objectClass ); for ( final PropertyDescriptor pd : bi.getPropertyDescriptors () ) { if ( pd.getWriteMethod () != null ) { final BindTarget target = createPropertyTarget ( o, pd ); final Binding binding = bindValue ( target, this.converter ); if ( binding != null ) { target.bind ( binding ); this.result.addChild ( pd.getName (), binding.getBindingResult () ); } } } validate ( o ); } protected void validate ( final Object o ) { if ( this.validator == null ) { return; } final ValidationResult vr = this.validator.validate ( o ); for ( final Map.Entry<String, List<BindingError>> entry : vr.getErrors ().entrySet () ) { this.result.addErrors ( entry.getKey (), entry.getValue () ); } this.result.addMarkers ( vr.getMarkers () ); } public BindingResult getResult () { return this.result; } public void setValidator ( final Validator validator ) { this.validator = validator; } }