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    }