001    /*
002     * Copyright © 2008, 2009 Pedro Agulló Soliveres.
003     * 
004     * This file is part of DirectJNgine.
005     *
006     * DirectJNgine is free software: you can redistribute it and/or modify
007     * it under the terms of the GNU Lesser General Public License as published by
008     * the Free Software Foundation, either version 3 of the License.
009     *
010     * Commercial use is permitted to the extent that the code/component(s)
011     * do NOT become part of another Open Source or Commercially developed
012     * licensed development library or toolkit without explicit permission.
013     *
014     * DirectJNgine is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017     * GNU Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public License
020     * along with DirectJNgine.  If not, see <http://www.gnu.org/licenses/>.
021     * 
022     * This software uses the ExtJs library (http://extjs.com), which is 
023     * distributed under the GPL v3 license (see http://extjs.com/license).
024     */
025    
026    package com.softwarementors.extjs.djn.router.dispatcher;
027    
028    import java.lang.reflect.Constructor;
029    import java.lang.reflect.InvocationTargetException;
030    import java.lang.reflect.Method;
031    import java.lang.reflect.Modifier;
032    import java.security.AccessController;
033    import java.security.PrivilegedAction;
034    
035    import com.softwarementors.extjs.djn.ClassUtils;
036    import com.softwarementors.extjs.djn.Timer;
037    import com.softwarementors.extjs.djn.api.RegisteredAction;
038    import com.softwarementors.extjs.djn.api.RegisteredMethod;
039    import com.softwarementors.extjs.djn.router.processor.RequestException;
040    
041    import edu.umd.cs.findbugs.annotations.NonNull;
042    
043    public abstract class DispatcherBase implements Dispatcher {
044      public Object dispatch( RegisteredMethod method, Object[] parameters ) {
045        assert method != null;
046        assert parameters != null;
047        
048        Method javaMethod = method.getMethod();
049        int expectedArgumentCount = method.getParameterCount();
050        if( parameters.length != expectedArgumentCount ) {
051          throw RequestException.forWrongMethodArgumentCount( method, parameters.length );
052        }
053        
054        Timer timer = new Timer();
055        try {
056          Object actionInstance = null;
057          if( !Modifier.isStatic(javaMethod.getModifiers())) {
058            actionInstance = getActionInstance(method.getAction());
059            assert actionInstance != null;
060          }
061          Object result = invokeMethod( method, actionInstance, parameters);
062          return result;
063        }
064        finally {
065          timer.stop();
066          if( Timer.logger.isDebugEnabled()) {
067            timer.logDebugTimeInMilliseconds("  - Java method dispatch time (" + ClassUtils.getSimpleName(javaMethod.getDeclaringClass()) + "." + method.getName() + ")" );
068          }
069        }
070      }
071    
072      protected abstract Object getActionInstance(RegisteredAction action);
073      
074      protected Object createActionInstance(RegisteredAction action) {
075        assert action != null;
076        
077        Class<?> instanceClass = action.getActionClass();
078        Object actionInstance;
079        try {
080          Constructor<?> c = instanceClass.getConstructor();
081          // Invoke private constructors too
082          boolean accessible = c.isAccessible();
083          try {
084            c.setAccessible(true);
085            actionInstance = c.newInstance();
086          }
087          finally {
088            c.setAccessible(accessible);
089          }
090          return actionInstance;
091        }
092        catch (InstantiationException e) {
093          throw createUnableToCreateActionInstanceException(action, e);
094        }
095        catch (IllegalAccessException e) {
096          throw createUnableToCreateActionInstanceException(action, e);
097        }
098        catch (SecurityException e) {
099          throw createUnableToCreateActionInstanceException(action, e);
100        }
101        catch (NoSuchMethodException e) {
102          throw createUnableToCreateActionInstanceException(action, e);
103        }
104        catch (IllegalArgumentException e) {
105          throw createUnableToCreateActionInstanceException(action, e);
106        }
107        catch (InvocationTargetException e) {
108          throw createUnableToCreateActionInstanceException(action, e);
109        }
110      }
111    
112      // This class is a gadget needed to invoke the Method.setAccessible method
113      // 'the correct way' according to FindBugs: it flags direct usage as dangerous.
114      private static class MethodVisibilityModifier implements PrivilegedAction<Object> {
115        private boolean accessible;
116        @NonNull private Method method;
117        
118        public MethodVisibilityModifier(Method method) {
119          this.method = method;
120        }
121        
122        public Object run() {
123          this.method.setAccessible(this.accessible);
124          return null;
125        }
126        
127        public void setAccessible( boolean accessible ) {
128          this.accessible = accessible;
129        }
130      }
131      
132      protected Object invokeMethod(RegisteredMethod method, Object actionInstance, Object[] parameters) {
133        assert method != null;
134        assert parameters != null;
135        
136        Method javaMethod = method.getMethod();
137        Object result;
138        boolean accessible = javaMethod.isAccessible();
139        MethodVisibilityModifier visibilityModifier = new MethodVisibilityModifier(javaMethod);
140        try {      
141          try {
142            visibilityModifier.setAccessible(true);
143            AccessController.doPrivileged(visibilityModifier);
144            result = javaMethod.invoke( actionInstance, parameters );
145          }
146          finally {
147            visibilityModifier.setAccessible(accessible);
148            AccessController.doPrivileged(visibilityModifier);
149          }
150        }
151        catch (IllegalArgumentException e) {
152          throw createJavaMethodInvocationError(method, e);
153        }
154        catch (IllegalAccessException e) {
155          throw createJavaMethodInvocationError(method, e);
156        }
157        catch (InvocationTargetException e) {
158          throw createJavaMethodInvocationError(method, e);
159        }
160        return result;
161      }
162    
163      protected static MethodExecutionException createUnableToCreateActionInstanceException(RegisteredAction action, Throwable e) {
164        assert action != null;
165        assert e != null;
166        
167        return MethodExecutionException.forUnableToCreateActionInstance( action, e );
168      }
169    
170      private static MethodExecutionException createJavaMethodInvocationError(RegisteredMethod method, Throwable e) {
171        assert method != null;
172        assert e != null;
173        
174        return MethodExecutionException.forJavaMethodInvocationError( method, e );
175      }
176    
177    }