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
015package org.apache.hivemind.impl;
016
017import java.util.Collection;
018import java.util.HashMap;
019import java.util.Iterator;
020import java.util.List;
021import java.util.Locale;
022import java.util.Map;
023
024import org.apache.commons.logging.Log;
025import org.apache.hivemind.ErrorHandler;
026import org.apache.hivemind.Location;
027import org.apache.hivemind.Occurances;
028import org.apache.hivemind.ShutdownCoordinator;
029import org.apache.hivemind.conditional.EvaluationContextImpl;
030import org.apache.hivemind.conditional.Node;
031import org.apache.hivemind.conditional.Parser;
032import org.apache.hivemind.internal.ConfigurationPoint;
033import org.apache.hivemind.internal.Module;
034import org.apache.hivemind.internal.RegistryInfrastructure;
035import org.apache.hivemind.internal.ServicePoint;
036import org.apache.hivemind.parse.ConfigurationPointDescriptor;
037import org.apache.hivemind.parse.ContributionDescriptor;
038import org.apache.hivemind.parse.DependencyDescriptor;
039import org.apache.hivemind.parse.ImplementationDescriptor;
040import org.apache.hivemind.parse.InstanceBuilder;
041import org.apache.hivemind.parse.InterceptorDescriptor;
042import org.apache.hivemind.parse.ModuleDescriptor;
043import org.apache.hivemind.parse.ServicePointDescriptor;
044import org.apache.hivemind.schema.Schema;
045import org.apache.hivemind.schema.impl.SchemaImpl;
046import 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 */
058public 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}