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 }