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