001    /*
002     * Copyright © 2008, 2012 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.scanner;
027    
028    import java.lang.reflect.Method;
029    import java.util.ArrayList;
030    import java.util.Collections;
031    import java.util.List;
032    
033    import org.apache.log4j.Logger;
034    
035    import com.softwarementors.extjs.djn.ClassUtils;
036    import com.softwarementors.extjs.djn.api.RegisteredAction;
037    import com.softwarementors.extjs.djn.api.RegisteredApi;
038    import com.softwarementors.extjs.djn.api.RegisteredPollMethod;
039    import com.softwarementors.extjs.djn.api.RegisteredStandardMethod;
040    import com.softwarementors.extjs.djn.api.Registry;
041    import com.softwarementors.extjs.djn.config.ApiConfiguration;
042    import com.softwarementors.extjs.djn.config.ApiConfigurationException;
043    import com.softwarementors.extjs.djn.config.annotations.DirectAction;
044    import com.softwarementors.extjs.djn.config.annotations.DirectFormPostMethod;
045    import com.softwarementors.extjs.djn.config.annotations.DirectMethod;
046    import com.softwarementors.extjs.djn.config.annotations.DirectPollMethod;
047    
048    import edu.umd.cs.findbugs.annotations.NonNull;
049    
050    public class Scanner {
051      @NonNull private static final Logger logger = Logger.getLogger( Scanner.class);
052      @NonNull private Registry registry;
053      
054      public Scanner(Registry registry) {
055        assert registry != null;
056        
057        this.registry = registry;
058      }
059      
060      public void scanAndRegisterApiConfigurations(List<ApiConfiguration> apiConfigurations) {
061        assert apiConfigurations != null;
062        
063        for( ApiConfiguration api: apiConfigurations ) {
064          scanAndRegisterApiConfiguration(api);
065        }
066      }
067    
068      /* package */ void scanAndRegisterApiConfiguration(ApiConfiguration api) {
069        assert api != null;
070        
071        if( this.registry.hasApi( api.getName() )) {
072          ApiConfigurationException ex = ApiConfigurationException.forApiAlreadyRegistered( api.getName());
073          logger.fatal( ex.getMessage(), ex );
074          throw ex;
075        }
076        
077        RegisteredApi registeredApi = this.registry.addApi( api.getName(), api.getApiFile(), api.getFullApiFileName(), api.getApiNamespace(), api.getActionsNamespace());
078        
079        List<Class<?>> actionClasses = api.getClasses();    
080        for( Class<?> cls : actionClasses ) {
081          assert cls != null;
082          scanAndRegisterActionClass(registeredApi, cls );
083        }
084      }
085    
086      public void scanAndRegisterActionClass( RegisteredApi api, Class<?> actionClass ) {
087        assert api != null;
088        assert actionClass != null;
089        
090        /*
091        Map<Class<?>, RegisteredAction> actionsByClass = new HashMap<Class<?>, RegisteredAction>();
092    
093        if( actionsByClass.containsKey( actionClass ) ) {
094          ApiConfigurationException ex = ApiConfigurationException.forClassAlreadyRegisteredAsAction(actionClass);
095          logger.fatal( ex.getMessage(), ex );
096          throw ex;
097        }
098        */
099        
100        if( logger.isDebugEnabled() ) {
101          logger.debug( "Scanning Java class: " + actionClass.getName() );
102        }
103        
104        List<RegisteredAction> actions = createActionsFromJavaClass(api, actionClass);
105        scanAndRegisterActionClass(actions);
106        //actionsByClass.put( actionClass, action);
107      }
108      
109      private List<RegisteredAction> createActionsFromJavaClass(RegisteredApi api, Class<?> actionClass) {
110        assert api != null;
111        assert actionClass != null;
112        
113        DirectAction actionAnnotation = actionClass.getAnnotation(DirectAction.class);
114        List<String> actionNames = new ArrayList<String>();
115        if( actionAnnotation != null ) {
116          Collections.addAll( actionNames, actionAnnotation.action() );
117        }
118        
119        if( actionNames.isEmpty()) {
120          actionNames.add( ClassUtils.getSimpleName(actionClass) );
121        }
122    
123        List<RegisteredAction> actions = new ArrayList<RegisteredAction>();
124        for( String actionName : actionNames ) {
125          if( this.registry.hasAction( actionName ) ) {
126            RegisteredAction existingAction = this.registry.getAction( actionName );
127            ApiConfigurationException ex = ApiConfigurationException.forActionAlreadyRegistered(actionName, actionClass, existingAction.getActionClass());
128            logger.fatal( ex.getMessage(), ex );
129            throw ex;
130          }
131          RegisteredAction action = api.addAction( actionClass, actionName );
132          actions.add( action );
133        }
134        
135        return actions;
136      }
137      
138      private static final String POLL_METHOD_NAME_PREFIX = "djnpoll_";
139      private static final String FORM_POST_METHOD_NAME_PREFIX = "djnform_";
140      private static final String STANDARD_METHOD_NAME_PREFIX = "djn_";
141    
142      private void scanAndRegisterActionClass(List<RegisteredAction> actions) {
143        assert actions != null;
144        assert !actions.isEmpty();
145        
146        RegisteredAction actionTemplate = actions.get(0);
147        
148        // *All* methods are candidates, including those in base classes, 
149        // even if the base class does not have a DirectAction annotation!
150        List<Method> allMethods = new ArrayList<Method>();
151        Class<?> cls = actionTemplate.getActionClass();
152        while( cls != null ) {
153          Method[] methods = cls.getDeclaredMethods(); // Get private, protected and other methods!
154          Collections.addAll( allMethods, methods );
155          cls = cls.getSuperclass();
156        }
157        
158        for( Method method : allMethods ) {
159          // Check if the kind of direct method -if any
160          DirectMethod methodAnnotation = method.getAnnotation(DirectMethod.class);
161          boolean isStandardMethod = methodAnnotation != null;
162          if( !isStandardMethod ) {
163            isStandardMethod = method.getName().startsWith(STANDARD_METHOD_NAME_PREFIX);
164          }
165          
166          DirectFormPostMethod postMethodAnnotation = method.getAnnotation(DirectFormPostMethod.class);
167          boolean isFormPostMethod = postMethodAnnotation != null;
168          if( !isFormPostMethod ) {
169            isFormPostMethod = method.getName().startsWith(FORM_POST_METHOD_NAME_PREFIX);  
170          }
171          
172          DirectPollMethod pollMethodAnnotation = method.getAnnotation(DirectPollMethod.class);
173          boolean isPollMethod = pollMethodAnnotation != null;
174          if( !isPollMethod ) {
175            isPollMethod = method.getName().startsWith( POLL_METHOD_NAME_PREFIX );
176          }
177          
178          // Check that a method is just of only one kind of method
179          if( isStandardMethod && isFormPostMethod ) {
180            ApiConfigurationException ex = ApiConfigurationException.forMethodCantBeStandardAndFormPostMethodAtTheSameTime(actionTemplate, method);
181            logger.fatal( ex.getMessage(), ex );
182            throw ex;
183          }
184          if( (methodAnnotation != null || postMethodAnnotation != null) && isPollMethod) {
185            ApiConfigurationException ex = ApiConfigurationException.forPollMethodCantBeStandardOrFormPostMethodAtTheSameTime(actionTemplate, method);
186            logger.fatal( ex.getMessage(), ex );
187            throw ex;
188          }
189    
190          // Process standard and form post methods together, as they are very similar
191          if( isStandardMethod || isFormPostMethod) {
192            
193            String methodName = "";
194            if( isStandardMethod ) {
195              methodName = getStandardMethodName(method, methodAnnotation);
196            }
197            else {
198              methodName = getFormPostMethodName( method, postMethodAnnotation);
199            }        
200            if( actionTemplate.hasStandardMethod(methodName)  ) {
201              ApiConfigurationException ex = ApiConfigurationException.forMethodAlreadyRegisteredInAction(methodName, actionTemplate.getName());
202              logger.fatal( ex.getMessage(), ex );
203              throw ex;
204            }
205            
206            if( isFormPostMethod && !RegisteredStandardMethod.isValidFormHandlingMethod(method)) {
207              ApiConfigurationException ex = ApiConfigurationException.forMethodHasWrongParametersForAFormHandler( actionTemplate.getName(), methodName );
208              logger.fatal( ex.getMessage(), ex );
209              throw ex;
210            }
211    
212            for( RegisteredAction actionToRegister : actions ) {
213              actionToRegister.addStandardMethod( methodName, method, isFormPostMethod );
214            }
215          }
216                
217          // Process "poll" method
218          if( isPollMethod ) {
219            for( RegisteredAction actionToRegister : actions ) {
220              createPollMethod(actionToRegister, method, pollMethodAnnotation);
221            }
222          }
223        }
224      }
225    
226      private String getFormPostMethodName(Method method, DirectFormPostMethod postMethodAnnotation) {
227        String methodName = "";
228        if( postMethodAnnotation != null ) {
229          methodName = postMethodAnnotation.method();
230        }
231        if( methodName.equals("" )) {
232          methodName = method.getName(); 
233        }
234        if( methodName.startsWith(FORM_POST_METHOD_NAME_PREFIX)) {
235          methodName = method.getName().substring(FORM_POST_METHOD_NAME_PREFIX.length());
236        }
237        return methodName;
238      }
239    
240      private String getStandardMethodName(Method method, DirectMethod methodAnnotation) {
241        String methodName = "";
242        if( methodAnnotation != null ) {
243          methodName = methodAnnotation.method();
244        }
245        if( methodName.equals("" )) {
246          methodName = method.getName(); 
247        }
248        if( methodName.startsWith(STANDARD_METHOD_NAME_PREFIX)) {
249          methodName = method.getName().substring(STANDARD_METHOD_NAME_PREFIX.length());
250        }
251        return methodName;
252      }
253    
254      private RegisteredPollMethod createPollMethod(RegisteredAction action, Method method, DirectPollMethod pollMethodAnnotation) {
255        assert action != null;
256        assert method != null;
257        
258        String eventName = getEventName(method, pollMethodAnnotation);
259            
260        if( this.registry.hasPollMethod(eventName)) {
261          ApiConfigurationException ex = ApiConfigurationException.forPollEventAlreadyRegistered( eventName );
262          logger.fatal( ex.getMessage(), ex );
263          throw ex;
264        }
265        
266        if( !RegisteredPollMethod.isValidPollMethod(method)) {
267          ApiConfigurationException ex = ApiConfigurationException.forMethodHasWrongParametersForAPollHandler( method );
268          logger.fatal( ex.getMessage(), ex );
269          throw ex;
270        }
271        
272        RegisteredPollMethod poll = action.addPollMethod( eventName, method);
273        return poll;    
274      }
275    
276      private String getEventName(Method method, DirectPollMethod pollMethodAnnotation) {
277        assert method != null;
278        
279        String eventName = "";
280        if( pollMethodAnnotation != null ) {
281          eventName = pollMethodAnnotation.event();
282        }
283        if( eventName.equals("")) {
284          eventName = method.getName();
285        }
286        if( eventName.startsWith(POLL_METHOD_NAME_PREFIX)) {
287          eventName = method.getName().substring(POLL_METHOD_NAME_PREFIX.length());
288        }
289        return eventName;
290      }
291    
292    }