001    // Copyright 2004, 2005 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.impl;
016    
017    import java.util.Collection;
018    import java.util.HashMap;
019    import java.util.Iterator;
020    import java.util.List;
021    import java.util.Locale;
022    import java.util.Map;
023    
024    import org.apache.commons.logging.Log;
025    import org.apache.hivemind.ErrorHandler;
026    import org.apache.hivemind.Location;
027    import org.apache.hivemind.Occurances;
028    import org.apache.hivemind.ShutdownCoordinator;
029    import org.apache.hivemind.conditional.EvaluationContextImpl;
030    import org.apache.hivemind.conditional.Node;
031    import org.apache.hivemind.conditional.Parser;
032    import org.apache.hivemind.internal.ConfigurationPoint;
033    import org.apache.hivemind.internal.Module;
034    import org.apache.hivemind.internal.RegistryInfrastructure;
035    import org.apache.hivemind.internal.ServicePoint;
036    import org.apache.hivemind.parse.ConfigurationPointDescriptor;
037    import org.apache.hivemind.parse.ContributionDescriptor;
038    import org.apache.hivemind.parse.DependencyDescriptor;
039    import org.apache.hivemind.parse.ImplementationDescriptor;
040    import org.apache.hivemind.parse.InstanceBuilder;
041    import org.apache.hivemind.parse.InterceptorDescriptor;
042    import org.apache.hivemind.parse.ModuleDescriptor;
043    import org.apache.hivemind.parse.ServicePointDescriptor;
044    import org.apache.hivemind.schema.Schema;
045    import org.apache.hivemind.schema.impl.SchemaImpl;
046    import org.apache.hivemind.util.IdUtils;
047    
048    /**
049     * Fed a series of {@link org.apache.hivemind.parse.ModuleDescriptor}s, this class will assemble
050     * them into a final {@link org.apache.hivemind.internal.RegistryInfrastructure} as well as perform
051     * some validations.
052     * <p>
053     * This class was extracted from {@link org.apache.hivemind.impl.RegistryBuilder}.
054     * 
055     * @author Howard M. Lewis Ship
056     * @since 1.1
057     */
058    public class RegistryInfrastructureConstructor
059    {
060        private ErrorHandler _errorHandler;
061    
062        private Log _log;
063    
064        private RegistryAssembly _assembly;
065    
066        /** @since 1.1 */
067    
068        private Parser _conditionalExpressionParser;
069    
070        public RegistryInfrastructureConstructor(ErrorHandler errorHandler, Log log,
071                RegistryAssembly assembly)
072        {
073            _errorHandler = errorHandler;
074            _log = log;
075            _assembly = assembly;
076        }
077    
078        /**
079         * Map of {@link ModuleDescriptor} keyed on module id.
080         */
081    
082        private Map _moduleDescriptors = new HashMap();
083    
084        /**
085         * Map of {@link ModuleImpl} keyed on module id.
086         */
087        private Map _modules = new HashMap();
088    
089        /**
090         * Map of {@link Schema} keyed on fully qualified module id.
091         */
092        private Map _schemas = new HashMap();
093    
094        /**
095         * Map of {@link ServicePointImpl} keyed on fully qualified id.
096         */
097    
098        private Map _servicePoints = new HashMap();
099    
100        /**
101         * Map of {@link ConfigurationPointImpl} keyed on fully qualified id.
102         */
103    
104        private Map _configurationPoints = new HashMap();
105    
106        /**
107         * Shutdown coordinator shared by all objects.
108         */
109    
110        private ShutdownCoordinator _shutdownCoordinator = new ShutdownCoordinatorImpl();
111    
112        /**
113         * This class is used to check the dependencies of a ModuleDescriptor. As the checker is run it
114         * will log errors to the ErrorHandler if dependencies don't resolve or the versions dont match.
115         */
116        private class ModuleDependencyChecker implements Runnable
117        {
118            private ModuleDescriptor _source;
119    
120            public ModuleDependencyChecker(ModuleDescriptor source)
121            {
122                _source = source;
123            }
124    
125            public void run()
126            {
127                List dependencies = _source.getDependencies();
128                int count = size(dependencies);
129    
130                for (int i = 0; i < count; i++)
131                {
132                    DependencyDescriptor dependency = (DependencyDescriptor) dependencies.get(i);
133                    checkDependency(dependency);
134                }
135            }
136    
137            private void checkDependency(DependencyDescriptor dependency)
138            {
139                ModuleDescriptor requiredModule = (ModuleDescriptor) _moduleDescriptors.get(dependency
140                        .getModuleId());
141    
142                if (requiredModule == null)
143                {
144                    _errorHandler.error(
145                            _log,
146                            ImplMessages.dependencyOnUnknownModule(dependency),
147                            dependency.getLocation(),
148                            null);
149                    return;
150                }
151    
152                if (dependency.getVersion() != null
153                        && !dependency.getVersion().equals(requiredModule.getVersion()))
154                {
155                    _errorHandler.error(
156                            _log,
157                            ImplMessages.dependencyVersionMismatch(dependency),
158                            dependency.getLocation(),
159                            null);
160                    return;
161                }
162            }
163        }
164    
165        /**
166         * Constructs the registry infrastructure, based on data collected during the prior calls to
167         * {@link #addModuleDescriptor(ModuleDescriptor)}. Expects that all post-processing of the
168         * {@link RegistryAssembly} has already occured.
169         */
170        public RegistryInfrastructure constructRegistryInfrastructure(Locale locale)
171        {
172            RegistryInfrastructureImpl result = new RegistryInfrastructureImpl(_errorHandler, locale);
173    
174            addServiceAndConfigurationPoints(result);
175    
176            addImplementationsAndContributions();
177    
178            checkForMissingServices();
179    
180            checkContributionCounts();
181    
182            result.setShutdownCoordinator(_shutdownCoordinator);
183    
184            addModulesToRegistry(result);
185    
186            // The caller is responsible for invoking startup().
187    
188            return result;
189        }
190    
191        public void addModuleDescriptor(ModuleDescriptor md)
192        {
193            String id = md.getModuleId();
194    
195            if (_log.isDebugEnabled())
196                _log.debug("Processing module " + id);
197    
198            if (_modules.containsKey(id))
199            {
200                Module existing = (Module) _modules.get(id);
201    
202                _errorHandler.error(_log, ImplMessages.duplicateModuleId(id, existing.getLocation(), md
203                        .getLocation()), null, null);
204    
205                // Ignore the duplicate module descriptor.
206                return;
207            }
208    
209            ModuleImpl module = new ModuleImpl();
210    
211            module.setLocation(md.getLocation());
212            module.setModuleId(id);
213            module.setPackageName(md.getPackageName());
214            module.setClassResolver(md.getClassResolver());
215    
216            if (size(md.getDependencies()) > 0)
217                _assembly.addPostProcessor(new ModuleDependencyChecker(md));
218    
219            for (Iterator schemas = md.getSchemas().iterator(); schemas.hasNext();)
220            {
221                SchemaImpl schema = (SchemaImpl) schemas.next();
222    
223                schema.setModule(module);
224    
225                _schemas.put(IdUtils.qualify(id, schema.getId()), schema);
226            }
227    
228            _modules.put(id, module);
229    
230            _moduleDescriptors.put(id, md);
231        }
232    
233        private void addServiceAndConfigurationPoints(RegistryInfrastructureImpl infrastructure)
234        {
235            for (Iterator i = _moduleDescriptors.values().iterator(); i.hasNext();)
236            {
237                ModuleDescriptor md = (ModuleDescriptor) i.next();
238    
239                String id = md.getModuleId();
240    
241                ModuleImpl module = (ModuleImpl) _modules.get(id);
242    
243                addServicePoints(infrastructure, module, md);
244    
245                addConfigurationPoints(infrastructure, module, md);
246            }
247        }
248    
249        private void addServicePoints(RegistryInfrastructureImpl infrastructure, Module module,
250                ModuleDescriptor md)
251        {
252            String moduleId = md.getModuleId();
253            List services = md.getServicePoints();
254            int count = size(services);
255    
256            for (int i = 0; i < count; i++)
257            {
258                ServicePointDescriptor sd = (ServicePointDescriptor) services.get(i);
259    
260                String pointId = moduleId + "." + sd.getId();
261    
262                ServicePoint existingPoint = (ServicePoint) _servicePoints.get(pointId);
263    
264                if (existingPoint != null)
265                {
266                    _errorHandler.error(_log, ImplMessages.duplicateExtensionPointId(
267                            pointId,
268                            existingPoint), sd.getLocation(), null);
269                    continue;
270                }
271    
272                if (_log.isDebugEnabled())
273                    _log.debug("Creating service point " + pointId);
274    
275                // Choose which class to instantiate based on
276                // whether the service is create-on-first-reference
277                // or create-on-first-use (deferred).
278    
279                ServicePointImpl point = new ServicePointImpl();
280    
281                point.setExtensionPointId(pointId);
282                point.setLocation(sd.getLocation());
283                point.setModule(module);
284    
285                point.setServiceInterfaceName(sd.getInterfaceClassName());
286    
287                point.setParametersSchema(findSchema(sd.getParametersSchema(), module, sd
288                        .getParametersSchemaId(), point.getLocation()));
289    
290                point.setParametersCount(sd.getParametersCount());
291                point.setVisibility(sd.getVisibility());
292    
293                point.setShutdownCoordinator(_shutdownCoordinator);
294    
295                infrastructure.addServicePoint(point);
296    
297                // Save this for the second phase, where contributions
298                // from other modules are applied.
299    
300                _servicePoints.put(pointId, point);
301    
302                addInternalImplementations(module, pointId, sd);
303            }
304        }
305    
306        private void addConfigurationPoints(RegistryInfrastructureImpl registry, Module module,
307                ModuleDescriptor md)
308        {
309            String moduleId = md.getModuleId();
310            List points = md.getConfigurationPoints();
311            int count = size(points);
312    
313            for (int i = 0; i < count; i++)
314            {
315                ConfigurationPointDescriptor cpd = (ConfigurationPointDescriptor) points.get(i);
316    
317                String pointId = moduleId + "." + cpd.getId();
318    
319                ConfigurationPoint existingPoint = (ConfigurationPoint) _configurationPoints
320                        .get(pointId);
321    
322                if (existingPoint != null)
323                {
324                    _errorHandler.error(_log, ImplMessages.duplicateExtensionPointId(
325                            pointId,
326                            existingPoint), cpd.getLocation(), null);
327                    continue;
328                }
329    
330                if (_log.isDebugEnabled())
331                    _log.debug("Creating configuration point " + pointId);
332    
333                ConfigurationPointImpl point = new ConfigurationPointImpl();
334    
335                point.setExtensionPointId(pointId);
336                point.setLocation(cpd.getLocation());
337                point.setModule(module);
338                point.setExpectedCount(cpd.getCount());
339    
340                point.setContributionsSchema(findSchema(cpd.getContributionsSchema(), module, cpd
341                        .getContributionsSchemaId(), cpd.getLocation()));
342    
343                point.setVisibility(cpd.getVisibility());
344    
345                point.setShutdownCoordinator(_shutdownCoordinator);
346    
347                registry.addConfigurationPoint(point);
348    
349                // Needed later when we reconcile the rest
350                // of the configuration contributions.
351    
352                _configurationPoints.put(pointId, point);
353            }
354        }
355    
356        private void addContributionElements(Module sourceModule, ConfigurationPointImpl point,
357                List elements)
358        {
359            if (size(elements) == 0)
360                return;
361    
362            if (_log.isDebugEnabled())
363                _log
364                        .debug("Adding contributions to configuration point "
365                                + point.getExtensionPointId());
366    
367            ContributionImpl c = new ContributionImpl();
368            c.setContributingModule(sourceModule);
369            c.addElements(elements);
370    
371            point.addContribution(c);
372        }
373    
374        private void addModulesToRegistry(RegistryInfrastructureImpl registry)
375        {
376            // Add each module to the registry.
377    
378            Iterator i = _modules.values().iterator();
379            while (i.hasNext())
380            {
381                ModuleImpl module = (ModuleImpl) i.next();
382    
383                if (_log.isDebugEnabled())
384                    _log.debug("Adding module " + module.getModuleId() + " to registry");
385    
386                module.setRegistry(registry);
387            }
388        }
389    
390        private void addImplementationsAndContributions()
391        {
392            for (Iterator i = _moduleDescriptors.values().iterator(); i.hasNext();)
393            {
394                ModuleDescriptor md = (ModuleDescriptor) i.next();
395    
396                if (_log.isDebugEnabled())
397                    _log.debug("Adding contributions from module " + md.getModuleId());
398    
399                addImplementations(md);
400                addContributions(md);
401            }
402        }
403    
404        private void addImplementations(ModuleDescriptor md)
405        {
406            String moduleId = md.getModuleId();
407            Module sourceModule = (Module) _modules.get(moduleId);
408    
409            List implementations = md.getImplementations();
410            int count = size(implementations);
411    
412            for (int i = 0; i < count; i++)
413            {
414                ImplementationDescriptor impl = (ImplementationDescriptor) implementations.get(i);
415    
416                if (!includeContribution(impl.getConditionalExpression(), sourceModule, impl
417                        .getLocation()))
418                    continue;
419    
420                String pointId = impl.getServiceId();
421                String qualifiedId = IdUtils.qualify(moduleId, pointId);
422    
423                addImplementations(sourceModule, qualifiedId, impl);
424            }
425    
426        }
427    
428        private void addContributions(ModuleDescriptor md)
429        {
430            String moduleId = md.getModuleId();
431            Module sourceModule = (Module) _modules.get(moduleId);
432    
433            List contributions = md.getContributions();
434            int count = size(contributions);
435    
436            for (int i = 0; i < count; i++)
437            {
438                ContributionDescriptor cd = (ContributionDescriptor) contributions.get(i);
439    
440                if (!includeContribution(cd.getConditionalExpression(), sourceModule, cd.getLocation()))
441                    continue;
442    
443                String pointId = cd.getConfigurationId();
444                String qualifiedId = IdUtils.qualify(moduleId, pointId);
445    
446                ConfigurationPointImpl point = (ConfigurationPointImpl) _configurationPoints
447                        .get(qualifiedId);
448    
449                if (point == null)
450                {
451                    _errorHandler.error(_log, ImplMessages.unknownConfigurationPoint(moduleId, cd), cd
452                            .getLocation(), null);
453    
454                    continue;
455                }
456    
457                if (!point.visibleToModule(sourceModule))
458                {
459                    _errorHandler.error(_log, ImplMessages.configurationPointNotVisible(
460                            point,
461                            sourceModule), cd.getLocation(), null);
462                    continue;
463                }
464    
465                addContributionElements(sourceModule, point, cd.getElements());
466            }
467        }
468    
469        private Schema findSchema(SchemaImpl schema, Module module, String schemaId, Location location)
470        {
471            if (schema != null)
472            {
473                schema.setModule(module);
474                return schema;
475            }
476    
477            if (schemaId == null)
478                return null;
479    
480            String moduleId = module.getModuleId();
481            String qualifiedId = IdUtils.qualify(moduleId, schemaId);
482    
483            return getSchema(qualifiedId, moduleId, location);
484        }
485    
486        private Schema getSchema(String schemaId, String referencingModule, Location reference)
487        {
488            Schema schema = (Schema) _schemas.get(schemaId);
489    
490            if (schema == null)
491                _errorHandler
492                        .error(_log, ImplMessages.unableToResolveSchema(schemaId), reference, null);
493            else if (!schema.visibleToModule(referencingModule))
494            {
495                _errorHandler.error(
496                        _log,
497                        ImplMessages.schemaNotVisible(schemaId, referencingModule),
498                        reference,
499                        null);
500                schema = null;
501            }
502    
503            return schema;
504        }
505    
506        /**
507         * Adds internal service contributions; the contributions provided inplace with the service
508         * definition.
509         */
510        private void addInternalImplementations(Module sourceModule, String pointId,
511                ServicePointDescriptor spd)
512        {
513            InstanceBuilder builder = spd.getInstanceBuilder();
514            List interceptors = spd.getInterceptors();
515    
516            if (builder == null && interceptors == null)
517                return;
518    
519            if (builder != null)
520                addServiceInstanceBuilder(sourceModule, pointId, builder, true);
521    
522            if (interceptors == null)
523                return;
524    
525            int count = size(interceptors);
526    
527            for (int i = 0; i < count; i++)
528            {
529                InterceptorDescriptor id = (InterceptorDescriptor) interceptors.get(i);
530                addInterceptor(sourceModule, pointId, id);
531            }
532        }
533    
534        /**
535         * Adds ordinary service contributions.
536         */
537    
538        private void addImplementations(Module sourceModule, String pointId, ImplementationDescriptor id)
539        {
540            InstanceBuilder builder = id.getInstanceBuilder();
541            List interceptors = id.getInterceptors();
542    
543            if (builder != null)
544                addServiceInstanceBuilder(sourceModule, pointId, builder, false);
545    
546            int count = size(interceptors);
547            for (int i = 0; i < count; i++)
548            {
549                InterceptorDescriptor ind = (InterceptorDescriptor) interceptors.get(i);
550    
551                addInterceptor(sourceModule, pointId, ind);
552            }
553        }
554    
555        /**
556         * Adds an {@link InstanceBuilder} to a service extension point.
557         */
558        private void addServiceInstanceBuilder(Module sourceModule, String pointId,
559                InstanceBuilder builder, boolean isDefault)
560        {
561            if (_log.isDebugEnabled())
562                _log.debug("Adding " + builder + " to service extension point " + pointId);
563    
564            ServicePointImpl point = (ServicePointImpl) _servicePoints.get(pointId);
565    
566            if (point == null)
567            {
568                _errorHandler.error(
569                        _log,
570                        ImplMessages.unknownServicePoint(sourceModule, pointId),
571                        builder.getLocation(),
572                        null);
573                return;
574            }
575    
576            if (!point.visibleToModule(sourceModule))
577            {
578                _errorHandler.error(
579                        _log,
580                        ImplMessages.servicePointNotVisible(point, sourceModule),
581                        builder.getLocation(),
582                        null);
583                return;
584            }
585    
586            if (point.getServiceConstructor(isDefault) != null)
587            {
588                _errorHandler.error(
589                        _log,
590                        ImplMessages.duplicateFactory(sourceModule, pointId, point),
591                        builder.getLocation(),
592                        null);
593    
594                return;
595            }
596    
597            point.setServiceModel(builder.getServiceModel());
598            point.setServiceConstructor(builder.createConstructor(point, sourceModule), isDefault);
599        }
600    
601        private void addInterceptor(Module sourceModule, String pointId, InterceptorDescriptor id)
602        {
603            if (_log.isDebugEnabled())
604                _log.debug("Adding " + id + " to service extension point " + pointId);
605    
606            ServicePointImpl point = (ServicePointImpl) _servicePoints.get(pointId);
607    
608            String sourceModuleId = sourceModule.getModuleId();
609    
610            if (point == null)
611            {
612                _errorHandler.error(_log, ImplMessages.unknownServicePoint(sourceModule, pointId), id
613                        .getLocation(), null);
614    
615                return;
616            }
617    
618            if (!point.visibleToModule(sourceModule))
619            {
620                _errorHandler.error(_log, ImplMessages.servicePointNotVisible(point, sourceModule), id
621                        .getLocation(), null);
622                return;
623            }
624    
625            ServiceInterceptorContributionImpl sic = new ServiceInterceptorContributionImpl();
626    
627            // Allow the factory id to be unqualified, to refer to an interceptor factory
628            // service from within the same module.
629    
630            sic.setFactoryServiceId(IdUtils.qualify(sourceModuleId, id.getFactoryServiceId()));
631            sic.setLocation(id.getLocation());
632    
633            sic.setFollowingInterceptorIds(IdUtils.qualifyList(sourceModuleId, id.getBefore()));
634            sic.setPrecedingInterceptorIds(IdUtils.qualifyList(sourceModuleId, id.getAfter()));
635            sic.setName(id.getName() != null ? IdUtils.qualify(sourceModuleId, id.getName()) : null);
636            sic.setContributingModule(sourceModule);
637            sic.setParameters(id.getParameters());
638    
639            point.addInterceptorContribution(sic);
640        }
641    
642        /**
643         * Checks that each service has at service constructor.
644         */
645        private void checkForMissingServices()
646        {
647            Iterator i = _servicePoints.values().iterator();
648            while (i.hasNext())
649            {
650                ServicePointImpl point = (ServicePointImpl) i.next();
651    
652                if (point.getServiceConstructor() != null)
653                    continue;
654    
655                _errorHandler.error(_log, ImplMessages.missingService(point), null, null);
656            }
657        }
658    
659        /**
660         * Checks that each configuration extension point has the right number of contributions.
661         */
662    
663        private void checkContributionCounts()
664        {
665            Iterator i = _configurationPoints.values().iterator();
666    
667            while (i.hasNext())
668            {
669                ConfigurationPointImpl point = (ConfigurationPointImpl) i.next();
670    
671                Occurances expected = point.getExpectedCount();
672    
673                int actual = point.getContributionCount();
674    
675                if (expected.inRange(actual))
676                    continue;
677    
678                _errorHandler.error(_log, ImplMessages.wrongNumberOfContributions(
679                        point,
680                        actual,
681                        expected), point.getLocation(), null);
682            }
683    
684        }
685    
686        /**
687         * Filters a contribution based on an expression. Returns true if the expression is null, or
688         * evaluates to true. Returns false if the expression if non-null and evaluates to false, or an
689         * exception occurs evaluating the expression.
690         * 
691         * @param expression
692         *            to parse and evaluate
693         * @param location
694         *            of the expression (used if an error is reported)
695         * @since 1.1
696         */
697    
698        private boolean includeContribution(String expression, Module module, Location location)
699        {
700            if (expression == null)
701                return true;
702    
703            if (_conditionalExpressionParser == null)
704                _conditionalExpressionParser = new Parser();
705    
706            try
707            {
708                Node node = _conditionalExpressionParser.parse(expression);
709    
710                return node.evaluate(new EvaluationContextImpl(module.getClassResolver()));
711            }
712            catch (RuntimeException ex)
713            {
714                _errorHandler.error(_log, ex.getMessage(), location, ex);
715    
716                return false;
717            }
718        }
719    
720        private static int size(Collection c)
721        {
722            return c == null ? 0 : c.size();
723        }
724    }