001    /*
002     * 
003     * This file is part of DirectJNgine.
004     *
005     * DirectJNgine is free software: you can redistribute it and/or modify
006     * it under the terms of the GNU General Public License as published by
007     * the Free Software Foundation, either version 3 of the License.
008     *
009     * Commercial use is permitted to the extent that the code/component(s)
010     * do NOT become part of another Open Source or Commercially developed
011     * licensed development library or toolkit without explicit permission.
012     *
013     * DirectJNgine is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with DirectJNgine.  If not, see <http://www.gnu.org/licenses/>.
020     * 
021     * This software uses the ExtJs library (http://extjs.com), which is 
022     * distributed under the GPL v3 license (see http://extjs.com/license).
023     */
024    
025    package com.softwarementors.extjs.djn.api;
026    
027    import java.lang.reflect.Method;
028    import java.util.ArrayList;
029    import java.util.HashMap;
030    import java.util.List;
031    import java.util.Map;
032    
033    import org.apache.log4j.Logger;
034    
035    import com.softwarementors.extjs.djn.ClassUtils;
036    import com.softwarementors.extjs.djn.StringUtils;
037    import com.softwarementors.extjs.djn.config.ApiConfiguration;
038    import com.softwarementors.extjs.djn.config.ApiConfigurationException;
039    import com.softwarementors.extjs.djn.config.GlobalConfiguration;
040    import com.softwarementors.extjs.djn.config.annotations.DirectAction;
041    import com.softwarementors.extjs.djn.config.annotations.DirectFormPostMethod;
042    import com.softwarementors.extjs.djn.config.annotations.DirectMethod;
043    import com.softwarementors.extjs.djn.config.annotations.DirectPollMethod;
044    
045    public class Registry {
046      
047      private static final Logger logger = Logger.getLogger( Registry.class);
048      
049      private Map<String, RegisteredAction> actionsByName = new HashMap<String,RegisteredAction>();
050      private Map<Class<?>, RegisteredAction> actionsByClass = new HashMap<Class<?>, RegisteredAction>();
051      private Map<String, RegisteredPollMethod> pollMethods = new HashMap<String, RegisteredPollMethod>();
052      private List<RegisteredAction> currentApiActions;
053      private List<RegisteredPollMethod> currentApiPollMethods;
054      private Map<String, RegisteredApi> apis = new HashMap<String, RegisteredApi>();
055      private GlobalConfiguration globalConfiguration;
056      
057      private boolean isActionClassRegistered( Class<?> actionClass ) {
058        assert actionClass != null;
059        
060        return this.actionsByClass.containsKey(actionClass);
061      }
062    
063      private RegisteredAction registerActionClass( Class<?> actionClass ) {
064        assert actionClass != null;
065        
066        if( isActionClassRegistered( actionClass ) ) {
067          ApiConfigurationException ex = ApiConfigurationException.forClassAlreadyRegisteredAsAction(actionClass);
068          logger.fatal( ex.getMessage(), ex );
069          throw ex;
070        }
071        
072        if( logger.isDebugEnabled() ) {
073          logger.debug( "Scanning Java class: " + actionClass.getName() );
074        }
075        
076        RegisteredAction action = createAction(actionClass );
077        String actionName = action.getName();
078        
079        if( isActionRegistered( actionName ) ) {
080          RegisteredAction existingAction = this.actionsByName.get( actionName );
081          ApiConfigurationException ex = ApiConfigurationException.forActionAlreadyRegistered(action, existingAction.getActionClass());
082          logger.fatal( ex.getMessage(), ex );
083          throw ex;
084        }
085        
086        this.actionsByClass.put( actionClass, action);
087        this.actionsByName.put( actionName, action );
088        this.currentApiActions.add( action );
089    
090        if( logger.isDebugEnabled() ) {
091          logger.debug( "Finished scanning Java class: " + actionClass.getName() );
092        }
093        
094        return action;
095      }
096      
097      private RegisteredAction createAction(Class<?> actionClass) {
098        assert actionClass != null;
099        
100        RegisteredAction action = createActionFromAnnotation(actionClass);
101        Map<String, RegisteredMethod> methodsByName = createMethods(action);
102        action.setMethods( methodsByName );
103        
104        return action;
105      }
106    
107      private RegisteredAction createActionFromAnnotation(Class<?> actionClass) {
108        assert actionClass != null;
109        
110        DirectAction actionAnnotation = actionClass.getAnnotation(DirectAction.class);
111        String actionName = "";
112        if( actionAnnotation != null ) {
113          actionName = actionAnnotation.action();      
114        }
115        
116        if( actionName.equals("")) {
117          actionName = ClassUtils.getSimpleName(actionClass);
118        }
119        return new RegisteredAction( actionClass, actionName );
120      }
121      
122      private static final String POLL_METHOD_NAME_PREFIX = "djnpoll_";
123      private static final String FORM_POST_METHOD_NAME_PREFIX = "djnform_";
124      private static final String STANDARD_METHOD_NAME_PREFIX = "djn_";
125    
126      private Map<String, RegisteredMethod> createMethods(RegisteredAction action) {
127        assert action != null;
128        
129        // *All* methods are candidates, including those in base classes, 
130        // even if the base class does not have a DirectAction annotation!
131        Method[] methods = action.getActionClass().getDeclaredMethods(); // Get private, protected and other methods!
132        
133        Map<String,RegisteredMethod> methodsByName = new HashMap<String,RegisteredMethod>();
134        for( Method method : methods ) {
135          // Check if the kind of direct method -if any
136          DirectMethod methodAnnotation = method.getAnnotation(DirectMethod.class);
137          boolean isStandardMethod = methodAnnotation != null;
138          if( !isStandardMethod ) {
139            isStandardMethod = method.getName().startsWith(STANDARD_METHOD_NAME_PREFIX);
140          }
141          
142          DirectFormPostMethod postMethodAnnotation = method.getAnnotation(DirectFormPostMethod.class);
143          boolean isFormPostMethod = postMethodAnnotation != null;
144          if( !isFormPostMethod ) {
145            isFormPostMethod = method.getName().startsWith(FORM_POST_METHOD_NAME_PREFIX);  
146          }
147          
148          DirectPollMethod pollMethodAnnotation = method.getAnnotation(DirectPollMethod.class);
149          boolean isPollMethod = pollMethodAnnotation != null;
150          if( !isPollMethod ) {
151            isPollMethod = method.getName().startsWith( POLL_METHOD_NAME_PREFIX );
152          }
153          
154          // Check that a method is just of only one kind of method
155          if( isStandardMethod && isFormPostMethod ) {
156            ApiConfigurationException ex = ApiConfigurationException.forMethodCantBeStandardAndFormPostMethodAtTheSameTime(action, method);
157            logger.fatal( ex.getMessage(), ex );
158            throw ex;
159          }
160          if( (methodAnnotation != null || postMethodAnnotation != null) && isPollMethod) {
161            ApiConfigurationException ex = ApiConfigurationException.forPollMethodCantBeStandardOrFormPostMethodAtTheSameTime(action, method);
162            logger.fatal( ex.getMessage(), ex );
163            throw ex;
164          }
165    
166          // Process standard and form post methods together, as they are very similar
167          if( isStandardMethod || isFormPostMethod) {
168            String methodName = "";
169            if( isStandardMethod ) {
170              methodName = getStandardMethodName(method, methodAnnotation);
171            }
172            else {
173              methodName = getFormPostMethodName( method, postMethodAnnotation);
174            }
175            RegisteredMethod actionMethod =  new RegisteredMethod( action, methodName, method, isFormPostMethod );
176            
177            if( methodsByName.containsKey(methodName) ) {
178              ApiConfigurationException ex = ApiConfigurationException.forMethodAlreadyRegisteredInAction(actionMethod);
179              logger.fatal( ex.getMessage(), ex );
180              throw ex;
181            }
182            
183            methodsByName.put( methodName, actionMethod);
184            
185            if( actionMethod.getFormHandler() ) {
186              if( logger.isDebugEnabled() ) {
187                logger.debug( "  - Registered new Form Method. Name: '" + actionMethod.getFullName() + "'. Java method: '" + action.getActionClass().getName() + "." + method.getName() + "'" );
188              }
189            }
190            else {
191              if( logger.isDebugEnabled() ) {
192                logger.debug( "  - Registered new Standard Method. Name: '" + actionMethod.getFullName() + "'. Java method: '" + action.getActionClass().getName() + "." + method.getName() + "'" );
193              }
194            }
195          }
196                
197          // Process "poll" method
198          if( isPollMethod ) {
199            processPollMethod(action, method, pollMethodAnnotation);
200          }
201        }
202        
203        return methodsByName;
204      }
205    
206      private String getFormPostMethodName(Method method, DirectFormPostMethod postMethodAnnotation) {
207        String methodName = "";
208        if( postMethodAnnotation != null ) {
209          methodName = postMethodAnnotation.method();
210        }
211        if( methodName.equals("" )) {
212          methodName = method.getName(); 
213        }
214        if( methodName.startsWith(FORM_POST_METHOD_NAME_PREFIX)) {
215          methodName = method.getName().substring(FORM_POST_METHOD_NAME_PREFIX.length());
216        }
217        return methodName;
218      }
219    
220      private String getStandardMethodName(Method method, DirectMethod methodAnnotation) {
221        String methodName = "";
222        if( methodAnnotation != null ) {
223          methodName = methodAnnotation.method();
224        }
225        if( methodName.equals("" )) {
226          methodName = method.getName(); 
227        }
228        if( methodName.startsWith(STANDARD_METHOD_NAME_PREFIX)) {
229          methodName = method.getName().substring(STANDARD_METHOD_NAME_PREFIX.length());
230        }
231        return methodName;
232      }
233    
234      private void processPollMethod(RegisteredAction action, Method method, DirectPollMethod pollMethodAnnotation) {
235        String eventName = getEventName(method, pollMethodAnnotation);
236        
237        RegisteredPollMethod poll = new RegisteredPollMethod( eventName, method, action.getActionClass());
238        
239        if( this.pollMethods.containsKey(poll.getName())) {
240          ApiConfigurationException ex = ApiConfigurationException.forPollEventAlreadyRegistered( poll );
241          logger.fatal( ex.getMessage(), ex );
242          throw ex;
243        }
244        
245        // Register in the list of poll methods/events
246        this.pollMethods.put( poll.getName(), poll );
247        this.currentApiPollMethods.add( poll );
248        
249        if( logger.isDebugEnabled() ) {
250          logger.debug( "  - Registered new Poll Method. Event name: '" + poll.getName() + "'. Java method: '" + action.getActionClass().getName() + "." + method.getName() + "'" );
251        }  
252      }
253    
254      private String getEventName(Method method, DirectPollMethod pollMethodAnnotation) {
255        String eventName = "";
256        if( pollMethodAnnotation != null ) {
257          eventName = pollMethodAnnotation.event();
258        }
259        if( eventName.equals("")) {
260          eventName = method.getName();
261        }
262        if( eventName.startsWith(POLL_METHOD_NAME_PREFIX)) {
263          eventName = method.getName().substring(POLL_METHOD_NAME_PREFIX.length());
264        }
265        return eventName;
266      }
267    
268      public Registry( GlobalConfiguration globalConfiguration, List<ApiConfiguration> apis ) {
269        assert globalConfiguration != null;
270        assert apis != null;
271        
272        this.globalConfiguration = globalConfiguration;
273        for( ApiConfiguration api: apis ) {
274          if( this.apis.containsKey( api.getName() )) {
275            ApiConfigurationException ex = ApiConfigurationException.forApiAlreadyRegistered( api.getName());
276            logger.fatal( ex.getMessage(), ex );
277            throw ex;
278          }
279    
280          this.currentApiActions = new ArrayList<RegisteredAction>();
281          this.currentApiPollMethods = new ArrayList<RegisteredPollMethod>();
282          List<Class<?>> actionClasses = api.getClasses();
283        
284          for( Class<?> cls : actionClasses ) {
285            assert cls != null;
286            registerActionClass(cls );
287          }
288          
289          RegisteredApi registeredApi = new RegisteredApi( api.getFullApiFileName(), api.getApiNamespace(), this.currentApiActions, api.getActionsNamespace(), this.currentApiPollMethods);
290          this.apis.put( api.getName(), registeredApi );
291        }
292      }
293    
294      /* package */ public RegisteredAction getAction( String actionName ) {
295        assert !StringUtils.isEmpty( actionName );
296        assert isActionRegistered( actionName );
297        
298        RegisteredAction action = this.actionsByName.get( actionName );
299        return action;
300      }
301    
302      private boolean isActionRegistered(String actionName) {
303        assert !StringUtils.isEmpty( actionName );
304    
305        return this.actionsByName.containsKey( actionName );
306      }
307    
308      public List<RegisteredAction> getActions() {
309        return new ArrayList<RegisteredAction>( this.actionsByName.values() );
310      }
311    
312      public RegisteredPollMethod getPollEvent(String eventName) {
313        assert eventName != null;
314        
315        return this.pollMethods.get(eventName);
316      }
317    
318      public List<RegisteredPollMethod> getPollMethods() {
319        return new ArrayList<RegisteredPollMethod>(this.pollMethods.values());
320      }
321      
322      public List<RegisteredApi> getApis() {
323        return new ArrayList<RegisteredApi>(this.apis.values());
324      }
325    
326      public GlobalConfiguration getGlobalConfiguration() {
327        return this.globalConfiguration;
328      }
329    
330    }