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.DirectPostMethod; 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 = createMethodsFromAnnotations(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 Map<String, RegisteredMethod> createMethodsFromAnnotations(RegisteredAction action) { 111 assert action != null; 112 113 // *All* methods are candidates, including those in base classes, 114 // even if the base class does not have a DirectAction annotation! 115 Method[] methods = action.getActionClass().getDeclaredMethods(); // Get private, protected and other methods! 116 117 Map<String,RegisteredMethod> methodsByName = new HashMap<String,RegisteredMethod>(); 118 for( Method method : methods ) { 119 DirectMethod methodAnnotation = method.getAnnotation(DirectMethod.class); 120 DirectPostMethod postMethodAnnotation = method.getAnnotation(DirectPostMethod.class); 121 if( methodAnnotation != null && postMethodAnnotation != null ) { 122 ApiConfigurationException ex = ApiConfigurationException.forMethodCantHaveDirectMethodAndDirectPostMethodAnnotationAtTheSameTime(action, method); 123 logger.fatal( ex.getMessage(), ex ); 124 throw ex; 125 } 126 127 // Process "normal" method 128 if( methodAnnotation != null || postMethodAnnotation != null) { 129 boolean formHandler = postMethodAnnotation != null; 130 String methodName = ""; 131 if( methodAnnotation != null ) { 132 methodName = methodAnnotation.method(); 133 } 134 else { 135 assert postMethodAnnotation != null; 136 methodName = postMethodAnnotation.method(); 137 } 138 if( methodName.equals("")) { 139 logger.trace( "No 'method' parameter specified in the '" + DirectMethod.class.getName() + "' or " + "'" + DirectPostMethod.class.getName() + "' annotation: using the Java method name ('" + methodName + "') as the method name" ); 140 methodName = method.getName(); 141 } 142 RegisteredMethod actionMethod = new RegisteredMethod( action, methodName, method, formHandler ); 143 144 logger.trace( "Processing Java method: " + method.toString() ); 145 146 if( methodsByName.containsKey(methodName) ) { 147 ApiConfigurationException ex = ApiConfigurationException.forMethodAlreadyRegisteredInAction(actionMethod); 148 logger.fatal( ex.getMessage(), ex ); 149 throw ex; 150 } 151 152 methodsByName.put( methodName, actionMethod); 153 } 154 155 156 DirectPollMethod pollMethodAnnotation = method.getAnnotation(DirectPollMethod.class); 157 if( (methodAnnotation != null || postMethodAnnotation != null) && pollMethodAnnotation != null) { 158 ApiConfigurationException ex = ApiConfigurationException.forPollMethodCantHaveDirectMethodOrDirectPostMethodAnnotationAtTheSameTime(action, method); 159 logger.fatal( ex.getMessage(), ex ); 160 throw ex; 161 } 162 163 // Process "poll" method 164 if( pollMethodAnnotation != null ) { 165 RegisteredPollMethod poll = createPollEventFromAnnotation( pollMethodAnnotation, method, action.getActionClass() ); 166 if( this.pollMethods.containsKey(poll.getName())) { 167 ApiConfigurationException ex = ApiConfigurationException.forPollEventAlreadyRegistered( poll ); 168 logger.fatal( ex.getMessage(), ex ); 169 throw ex; 170 } 171 172 // Register in the list of poll methods/events 173 this.pollMethods.put( poll.getName(), poll ); 174 this.currentApiPollMethods.add( poll ); 175 } 176 } 177 178 return methodsByName; 179 } 180 181 private RegisteredPollMethod createPollEventFromAnnotation(DirectPollMethod pollMethodAnnotation, Method method, Class<?> owningClass) { 182 assert pollMethodAnnotation != null; 183 assert method != null; 184 assert owningClass != null; 185 186 String eventName = pollMethodAnnotation.event(); 187 if( eventName.equals("")) { 188 eventName = method.getName(); 189 } 190 191 return new RegisteredPollMethod( eventName, method, owningClass); 192 } 193 194 public Registry( List<ApiConfiguration> apis ) { 195 assert apis != null; 196 197 for( ApiConfiguration api: apis ) { 198 if( this.apis.containsKey( api.getName() )) { 199 ApiConfigurationException ex = ApiConfigurationException.forApiAlreadyRegistered( api.getName()); 200 logger.fatal( ex.getMessage(), ex ); 201 throw ex; 202 } 203 204 this.currentApiActions = new ArrayList<RegisteredAction>(); 205 this.currentApiPollMethods = new ArrayList<RegisteredPollMethod>(); 206 List<Class<?>> actionClasses = api.getClasses(); 207 if( logger.isTraceEnabled() ) { 208 for( Class<?> cls : actionClasses ) { 209 assert cls != null; 210 logger.trace( "Candidate class: '" + cls.getName() + "'"); 211 } 212 } 213 214 if( actionClasses.size() == 0 ) { 215 logger.info( "There are no action classes to register"); 216 } 217 for( Class<?> cls : actionClasses ) { 218 assert cls != null; 219 registerActionClass(cls ); 220 } 221 222 RegisteredApi registeredApi = new RegisteredApi( api.getFullApiFileName(), api.getNamespace(), this.currentApiActions, this.currentApiPollMethods); 223 this.apis.put( api.getName(), registeredApi ); 224 } 225 } 226 227 /* package */ public RegisteredAction getAction( String actionName ) { 228 assert !StringUtils.isEmpty( actionName ); 229 assert isActionRegistered( actionName ); 230 231 RegisteredAction action = this.actionsByName.get( actionName ); 232 return action; 233 } 234 235 private boolean isActionRegistered(String actionName) { 236 assert !StringUtils.isEmpty( actionName ); 237 238 return this.actionsByName.containsKey( actionName ); 239 } 240 241 public List<RegisteredAction> getActions() { 242 return new ArrayList<RegisteredAction>( this.actionsByName.values() ); 243 } 244 245 public RegisteredPollMethod getPollEvent(String eventName) { 246 assert eventName != null; 247 248 return this.pollMethods.get(eventName); 249 } 250 251 public List<RegisteredPollMethod> getPollMethods() { 252 return new ArrayList<RegisteredPollMethod>(this.pollMethods.values()); 253 } 254 255 public List<RegisteredApi> getApis() { 256 return new ArrayList<RegisteredApi>(this.apis.values()); 257 } 258 259 public void updateJavascriptApiFiles(GlobalConfiguration globalConfiguration ) throws IOException { 260 assert globalConfiguration != null; 261 262 for( RegisteredApi api : getApis() ) { 263 ApiCodeGenerator generator = new ApiCodeGenerator( globalConfiguration, api ); 264 StringBuilder result = new StringBuilder(); 265 generator.appendCode(result); 266 saveToFile( api.getFullApiFileName(), result ); 267 } 268 } 269 270 private void saveToFile( String fullFileName, StringBuilder result ) throws IOException { 271 assert !StringUtils.isEmpty(fullFileName); 272 273 File file = new File( fullFileName ); 274 275 String code = result.toString(); 276 if( fileNeedsUpdating(file, code) ) { 277 FileUtils.writeStringToFile(file, code); 278 logger.info( "Saved generated ExtJs Direct API file to '" + file.getAbsolutePath() + '\''); 279 } 280 else { 281 logger.info( "File '" + file.getAbsolutePath() + '\'' + " is up to date: it was not rewritten."); 282 } 283 } 284 285 private boolean fileNeedsUpdating(File file, String code) throws IOException { 286 if( file.exists()) { 287 String contents = FileUtils.readFileToString(file); 288 return !contents.equals( code ); 289 } 290 return true; 291 } 292 293 294 }