001 // Copyright 2007 The Apache Software Foundation 002 // 003 // Licensed under the Apache License, Version 2.0 (the "License"); 004 // you may not use this file except in compliance with the License. 005 // You may obtain a copy of the License at 006 // 007 // http://www.apache.org/licenses/LICENSE-2.0 008 // 009 // Unless required by applicable law or agreed to in writing, software 010 // distributed under the License is distributed on an "AS IS" BASIS, 011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 // See the License for the specific language governing permissions and 013 // limitations under the License. 014 015 package org.apache.hivemind.annotations.internal; 016 017 import java.lang.annotation.Annotation; 018 import java.lang.reflect.Method; 019 import java.lang.reflect.Modifier; 020 021 import org.apache.commons.logging.Log; 022 import org.apache.commons.logging.LogFactory; 023 import org.apache.hivemind.ApplicationRuntimeException; 024 import org.apache.hivemind.ClassResolver; 025 import org.apache.hivemind.ErrorHandler; 026 import org.apache.hivemind.Location; 027 import org.apache.hivemind.Resource; 028 import org.apache.hivemind.annotations.AnnotationsMessages; 029 import org.apache.hivemind.annotations.definition.Configuration; 030 import org.apache.hivemind.annotations.definition.Module; 031 import org.apache.hivemind.annotations.definition.Service; 032 import org.apache.hivemind.annotations.definition.Submodule; 033 import org.apache.hivemind.definition.Contribution; 034 import org.apache.hivemind.definition.ImplementationConstructor; 035 import org.apache.hivemind.definition.ImplementationDefinition; 036 import org.apache.hivemind.definition.Occurances; 037 import org.apache.hivemind.definition.RegistryDefinition; 038 import org.apache.hivemind.definition.Visibility; 039 import org.apache.hivemind.definition.impl.ConfigurationPointDefinitionImpl; 040 import org.apache.hivemind.definition.impl.ContributionDefinitionImpl; 041 import org.apache.hivemind.definition.impl.ModuleDefinitionImpl; 042 import org.apache.hivemind.definition.impl.ImplementationDefinitionImpl; 043 import org.apache.hivemind.definition.impl.ServicePointDefinitionImpl; 044 import org.apache.hivemind.util.ClasspathResource; 045 import org.apache.hivemind.util.IdUtils; 046 047 /** 048 * Does the work for {@link org.apache.hivemind.annotations.AnnotatedModuleReader}. Processes an 049 * annotated class and registers the defined extension and extension points in a registry 050 * definition. 051 * The construction of extension points and extensions bases on reflective method calls 052 * to an instance of the module class. The module instance is created by a 053 * {@link ModuleInstanceProvider} during registry construction. 054 * 055 * @author Achim Huegen 056 */ 057 public class AnnotatedModuleProcessor 058 { 059 private static final Log _log = LogFactory.getLog(AnnotatedModuleProcessor.class); 060 061 private ClassResolver _classResolver; 062 063 private ErrorHandler _errorHandler; 064 065 private RegistryDefinition _registryDefinition; 066 067 public AnnotatedModuleProcessor(RegistryDefinition registryDefinition, 068 ClassResolver classResolver, ErrorHandler errorHandler) 069 { 070 _registryDefinition = registryDefinition; 071 _classResolver = classResolver; 072 _errorHandler = errorHandler; 073 } 074 075 public void processModule(Class moduleClass) 076 { 077 String moduleId = determineModuleId(moduleClass); 078 processModule(moduleClass, moduleId); 079 } 080 081 /** 082 * Processes a module. Inspects the class. 083 * 084 * @param moduleClass 085 */ 086 public void processModule(Class moduleClass, String moduleId) 087 { 088 checkModuleClassPrerequisites(moduleClass); 089 090 ModuleDefinitionImpl module = new ModuleDefinitionImpl(moduleId, 091 createModuleLocation(moduleClass), _classResolver, moduleClass.getPackage().getName()); 092 093 // processServices(moduleClass); 094 095 ModuleInstanceProvider instanceProvider = new ModuleInstanceProviderImpl(moduleClass, 096 module.getId()); 097 // Register provider as initialization provider so it can acquire a reference to the 098 // registry 099 _registryDefinition.addRegistryInitializationListener(instanceProvider); 100 101 processModuleMethods(moduleClass, module, instanceProvider); 102 _registryDefinition.addModule(module); 103 104 } 105 106 /** 107 * Ensures that a module class fulfills all prerequisites. 108 * 109 * @param moduleClass 110 */ 111 protected void checkModuleClassPrerequisites(Class moduleClass) 112 { 113 // These modifiers are allowed 114 final int validModifiers = Modifier.PUBLIC; 115 116 int invalidModifiers = moduleClass.getModifiers() & ~validModifiers; 117 if (invalidModifiers > 0) { 118 throw new ApplicationRuntimeException(AnnotationsMessages.moduleClassHasInvalidModifiers(moduleClass, invalidModifiers)); 119 } 120 121 // Check for package-private access 122 if ((moduleClass.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE)) == 0) { 123 throw new ApplicationRuntimeException(AnnotationsMessages.moduleClassIsPackagePrivate(moduleClass)); 124 } 125 } 126 127 private void processModuleMethods(Class moduleClass, ModuleDefinitionImpl module, 128 ModuleInstanceProvider instanceProvider) 129 { 130 // We need access to protected methods via getDeclaredMethods 131 // That means we must visit all superclasses manually 132 Method[] methods = moduleClass.getDeclaredMethods(); 133 for (int i = 0; i < methods.length; i++) 134 { 135 Method method = methods[i]; 136 processMethod(method, module, instanceProvider); 137 // Process superclass 138 Class superClass = moduleClass.getSuperclass(); 139 if (!superClass.equals(Object.class)) { 140 processModuleMethods(superClass, module, instanceProvider); 141 } 142 } 143 } 144 145 private void processMethod(Method method, ModuleDefinitionImpl module, 146 ModuleInstanceProvider instanceProvider) 147 { 148 if (_log.isDebugEnabled()) 149 { 150 _log.debug("Checking method " + method.getName() + " for annotations"); 151 } 152 153 Annotation[] annotations = method.getAnnotations(); 154 for (int j = 0; j < annotations.length; j++) 155 { 156 Annotation annotation = annotations[j]; 157 158 if (Service.class.equals(annotation.annotationType())) 159 { 160 processAnnotatedServiceMethod( 161 method, 162 (Service) annotation, 163 module, 164 instanceProvider); 165 } 166 else if (Configuration.class.equals(annotation.annotationType())) 167 { 168 processAnnotatedConfigurationMethod( 169 method, 170 (Configuration) annotation, 171 module, 172 instanceProvider); 173 } 174 else if (org.apache.hivemind.annotations.definition.Contribution.class.equals(annotation.annotationType())) 175 { 176 processAnnotatedContributionMethod( 177 method, 178 (org.apache.hivemind.annotations.definition.Contribution) annotation, 179 module, 180 instanceProvider); 181 } 182 else if (Submodule.class.equals(annotation.annotationType())) 183 { 184 processAnnotatedSubmoduleMethod( 185 method, 186 (Submodule) annotation, 187 module, 188 instanceProvider); 189 } 190 } 191 192 } 193 194 /** 195 * Ensures that an annotated method has only allowed modifiers. 196 * By default Modifier.PUBLIC and Modifier.PROTECTED are allowed. 197 * @param method the method 198 * @param allowedModifiers allowed {@link Modifier modifiers}. 199 * @param methodType used in error messages to describe what the method is used for 200 */ 201 protected void checkMethodModifiers(Method method, int allowedModifiers, String methodType) 202 { 203 // These modifiers are allowed 204 final int validModifiers = Modifier.PUBLIC | Modifier.PROTECTED | allowedModifiers; 205 206 int invalidModifiers = method.getModifiers() & ~validModifiers; 207 if (invalidModifiers > 0) { 208 throw new ApplicationRuntimeException(AnnotationsMessages.annotatedMethodHasInvalidModifiers(method, methodType, invalidModifiers)); 209 } 210 211 // TODO: Check for package access 212 213 // Check for setAccessible-Errors when Modifier.PROTECTED is used 214 if (Modifier.isProtected(method.getModifiers())) { 215 // Try to set method accessible 216 try 217 { 218 method.setAccessible(true); 219 } 220 catch (SecurityException e) 221 { 222 throw new ApplicationRuntimeException(AnnotationsMessages.annotatedMethodIsProtectedAndNotAccessible(method, methodType)); 223 } 224 } 225 } 226 227 private void processAnnotatedServiceMethod(Method method, Service service, 228 ModuleDefinitionImpl module, ModuleInstanceProvider instanceProvider) 229 { 230 checkMethodModifiers(method, 0, "service point"); 231 232 if (_log.isDebugEnabled()) 233 { 234 _log.debug("Method " + method.getName() + "classified as service point."); 235 } 236 237 Location location = new AnnotatedModuleLocation(module.getLocation().getResource(), 238 method.getDeclaringClass(), method); 239 240 Visibility visibility = Visibility.PUBLIC; 241 if (Modifier.isProtected(method.getModifiers())) { 242 visibility = Visibility.PRIVATE; 243 } 244 ServicePointDefinitionImpl spd = new ServicePointDefinitionImpl(module, service.id(), location, 245 visibility, method.getReturnType().getName()); 246 module.addServicePoint(spd); 247 248 ImplementationConstructor constructor = new MethodCallImplementationConstructor(location, 249 method, instanceProvider); 250 251 ImplementationDefinition sid = new ImplementationDefinitionImpl(module, location, 252 constructor, service.serviceModel(), true); 253 254 spd.addImplementation(sid); 255 256 } 257 258 private void processAnnotatedConfigurationMethod(Method method, Configuration configuration, ModuleDefinitionImpl module, ModuleInstanceProvider instanceProvider) 259 { 260 checkMethodModifiers(method, 0, "configuration point"); 261 262 if (_log.isDebugEnabled()) 263 { 264 _log.debug("Method " + method.getName() + "classified as configuration point."); 265 } 266 267 Location location = new AnnotatedModuleLocation(module.getLocation().getResource(), 268 method.getDeclaringClass(), method); 269 270 Visibility visibility = Visibility.PUBLIC; 271 if (Modifier.isProtected(method.getModifiers())) { 272 visibility = Visibility.PRIVATE; 273 } 274 ConfigurationPointDefinitionImpl cpd = new ConfigurationPointDefinitionImpl(module, configuration.id(), 275 location, visibility, method.getReturnType().getName(), Occurances.UNBOUNDED); 276 module.addConfigurationPoint(cpd); 277 278 // Add method implementation as initial contribution 279 Contribution contribution = new MethodCallContributionConstructor( 280 location, method, instanceProvider); 281 ContributionDefinitionImpl cd = new ContributionDefinitionImpl(module, location, contribution, true); 282 cpd.addContribution(cd); 283 } 284 285 private void processAnnotatedContributionMethod(Method method, org.apache.hivemind.annotations.definition.Contribution contribution, ModuleDefinitionImpl module, ModuleInstanceProvider instanceProvider) 286 { 287 checkMethodModifiers(method, 0, "contribution"); 288 289 if (_log.isDebugEnabled()) 290 { 291 _log.debug("Method " + method.getName() + "classified as contribution."); 292 } 293 294 Location location = new AnnotatedModuleLocation(module.getLocation().getResource(), 295 method.getDeclaringClass(), method); 296 297 Contribution constructor = new MethodCallContributionConstructor( 298 location, method, instanceProvider); 299 300 ContributionDefinitionImpl cd = new ContributionDefinitionImpl(module, location, constructor, false); 301 String qualifiedConfigurationId = IdUtils.qualify( 302 module.getId(), 303 contribution.configurationId()); 304 module.addContribution(qualifiedConfigurationId, cd); 305 306 } 307 308 /** 309 * Processes a method that is marked as submodule definition. 310 */ 311 private void processAnnotatedSubmoduleMethod(Method method, Submodule submodule, ModuleDefinitionImpl module, ModuleInstanceProvider instanceProvider) 312 { 313 checkMethodModifiers(method, 0, "submodule"); 314 315 if (_log.isDebugEnabled()) 316 { 317 _log.debug("Method " + method.getName() + "classified as submodule."); 318 } 319 320 String fullModuleId = IdUtils.qualify( 321 module.getId(), 322 submodule.id()); 323 // TODO: Check if return type is defined 324 AnnotatedModuleProcessor submoduleProcessor = new AnnotatedModuleProcessor(_registryDefinition, 325 _classResolver, _errorHandler); 326 submoduleProcessor.processModule(method.getReturnType(), fullModuleId); 327 } 328 329 /** 330 * Creates a location pointing at the module class. 331 */ 332 protected Location createModuleLocation(Class moduleClass) 333 { 334 String path = "/" + moduleClass.getName().replace('.', '/'); 335 336 Resource r = new ClasspathResource(_classResolver, path); 337 338 return new AnnotatedModuleLocation(r, moduleClass); 339 } 340 341 /** 342 * Determines the module id of the module defined by the annotated class. 343 * First priority has a {@link Module} annotation. If none is defined the 344 * id is determined from class and package name. 345 * 346 * @param moduleClass 347 * the module class 348 * @return the id 349 */ 350 private String determineModuleId(Class moduleClass) 351 { 352 Module moduleAnnotation = (Module) moduleClass.getAnnotation(Module.class); 353 if (moduleAnnotation != null) { 354 return moduleAnnotation.id(); 355 } else { 356 return getDefaultModuleId(moduleClass); 357 } 358 } 359 360 private String getDefaultModuleId(Class moduleClass) 361 { 362 return moduleClass.getName(); 363 } 364 }