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