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.Collections;
018import java.util.HashMap;
019import java.util.Iterator;
020import java.util.LinkedList;
021import java.util.List;
022import java.util.Locale;
023import java.util.Map;
024
025import org.apache.commons.logging.LogFactory;
026import org.apache.hivemind.ApplicationRuntimeException;
027import org.apache.hivemind.ErrorHandler;
028import org.apache.hivemind.HiveMindMessages;
029import org.apache.hivemind.Location;
030import org.apache.hivemind.ShutdownCoordinator;
031import org.apache.hivemind.SymbolSource;
032import org.apache.hivemind.SymbolSourceContribution;
033import org.apache.hivemind.internal.ConfigurationPoint;
034import org.apache.hivemind.internal.Module;
035import org.apache.hivemind.internal.RegistryInfrastructure;
036import org.apache.hivemind.internal.ServiceModelFactory;
037import org.apache.hivemind.internal.ServicePoint;
038import org.apache.hivemind.internal.ser.ServiceSerializationHelper;
039import org.apache.hivemind.internal.ser.ServiceSerializationSupport;
040import org.apache.hivemind.internal.ser.ServiceToken;
041import org.apache.hivemind.order.Orderer;
042import org.apache.hivemind.schema.Translator;
043import org.apache.hivemind.service.ThreadEventNotifier;
044import org.apache.hivemind.util.Defense;
045import org.apache.hivemind.util.PropertyUtils;
046import org.apache.hivemind.util.ToStringBuilder;
047
048/**
049 * Implementation of {@link RegistryInfrastructure}.
050 * 
051 * @author Howard Lewis Ship
052 */
053public final class RegistryInfrastructureImpl implements RegistryInfrastructure,
054        ServiceSerializationSupport
055{
056    private static final String SYMBOL_SOURCES = "hivemind.SymbolSources";
057
058    /**
059     * Map of {@link ServicePoint} keyed on fully qualified service id.
060     */
061    private Map _servicePoints = new HashMap();
062
063    /**
064     * Map of List (of {@link ServicePoint}, keyed on class name service interface.
065     */
066    private Map _servicePointsByInterfaceClassName = new HashMap();
067
068    /**
069     * Map of {@link ConfigurationPoint} keyed on fully qualified configuration id.
070     */
071    private Map _configurationPoints = new HashMap();
072
073    private SymbolSource[] _variableSources;
074
075    private ErrorHandler _errorHandler;
076
077    private Locale _locale;
078
079    private ShutdownCoordinator _shutdownCoordinator;
080
081    /**
082     * Map of {@link org.apache.hivemind.internal.ser.ServiceToken}, keyed on service id.
083     * 
084     * @since 1.1
085     */
086
087    private Map _serviceTokens;
088
089    /**
090     * Map of {@link ServiceModelFactory}, keyed on service model name, loaded from
091     * <code>hivemind.ServiceModels</code> configuration point.
092     */
093    private Map _serviceModelFactories;
094
095    private boolean _started = false;
096
097    private boolean _shutdown = false;
098
099    private ThreadEventNotifier _threadEventNotifier;
100
101    private TranslatorManager _translatorManager;
102
103    private SymbolExpander _expander;
104
105    public RegistryInfrastructureImpl(ErrorHandler errorHandler, Locale locale)
106    {
107        _errorHandler = errorHandler;
108        _locale = locale;
109
110        _translatorManager = new TranslatorManager(this, errorHandler);
111
112        _expander = new SymbolExpander(_errorHandler, this);
113    }
114
115    public Locale getLocale()
116    {
117        return _locale;
118    }
119
120    public void addServicePoint(ServicePoint point)
121    {
122        checkStarted();
123
124        _servicePoints.put(point.getExtensionPointId(), point);
125
126        addServicePointByInterface(point);
127    }
128
129    private void addServicePointByInterface(ServicePoint point)
130    {
131        String key = point.getServiceInterfaceClassName();
132
133        List l = (List) _servicePointsByInterfaceClassName.get(key);
134
135        if (l == null)
136        {
137            l = new LinkedList();
138            _servicePointsByInterfaceClassName.put(key, l);
139        }
140
141        l.add(point);
142    }
143
144    public void addConfigurationPoint(ConfigurationPoint point)
145    {
146        checkStarted();
147
148        _configurationPoints.put(point.getExtensionPointId(), point);
149    }
150
151    public ServicePoint getServicePoint(String serviceId, Module module)
152    {
153        checkShutdown();
154        ServicePoint result = (ServicePoint) _servicePoints.get(serviceId);
155        if (result == null)
156        {
157            if (serviceId.indexOf('.') == -1)
158            {
159                final List possibleMatches = getMatchingServiceIds(serviceId);
160                if (!possibleMatches.isEmpty())
161                {
162                    final StringBuffer sb = new StringBuffer();
163                    for (Iterator i = possibleMatches.iterator(); i.hasNext();)
164                    {
165                        final String matching = (String) i.next();
166                        sb.append('\"');
167                        sb.append(matching);
168                        sb.append('\"');
169                        if (i.hasNext())
170                        {
171                            sb.append(", ");
172                        }
173                    }
174                    throw new ApplicationRuntimeException(ImplMessages.unqualifiedServicePoint(
175                            serviceId,
176                            sb.toString()));
177                }
178            }
179            throw new ApplicationRuntimeException(ImplMessages.noSuchServicePoint(serviceId));
180        }
181
182        if (!result.visibleToModule(module))
183            throw new ApplicationRuntimeException(ImplMessages.serviceNotVisible(serviceId, module));
184
185        return result;
186    }
187
188    private List getMatchingServiceIds(String serviceId)
189    {
190        final List possibleMatches = new LinkedList();
191        for (Iterator i = _servicePoints.values().iterator(); i.hasNext();)
192        {
193            final ServicePoint servicePoint = (ServicePoint) i.next();
194            if (servicePoint.getExtensionPointId().equals(
195                    servicePoint.getModule().getModuleId() + "." + serviceId))
196            {
197                possibleMatches.add(servicePoint.getExtensionPointId());
198            }
199        }
200        return possibleMatches;
201    }
202
203    public Object getService(String serviceId, Class serviceInterface, Module module)
204    {
205        ServicePoint point = getServicePoint(serviceId, module);
206
207        return point.getService(serviceInterface);
208    }
209
210    public Object getService(Class serviceInterface, Module module)
211    {
212        String key = serviceInterface.getName();
213
214        List servicePoints = (List) _servicePointsByInterfaceClassName.get(key);
215
216        if (servicePoints == null)
217            servicePoints = Collections.EMPTY_LIST;
218
219        ServicePoint point = null;
220        int count = 0;
221
222        Iterator i = servicePoints.iterator();
223        while (i.hasNext())
224        {
225            ServicePoint sp = (ServicePoint) i.next();
226
227            if (!sp.visibleToModule(module))
228                continue;
229
230            point = sp;
231
232            count++;
233        }
234
235        if (count == 0)
236            throw new ApplicationRuntimeException(ImplMessages
237                    .noServicePointForInterface(serviceInterface));
238
239        if (count > 1)
240            throw new ApplicationRuntimeException(ImplMessages.multipleServicePointsForInterface(
241                    serviceInterface,
242                    servicePoints));
243
244        return point.getService(serviceInterface);
245    }
246
247    public ConfigurationPoint getConfigurationPoint(String configurationId, Module module)
248    {
249        checkShutdown();
250
251        ConfigurationPoint result = (ConfigurationPoint) _configurationPoints.get(configurationId);
252
253        if (result == null)
254            throw new ApplicationRuntimeException(ImplMessages.noSuchConfiguration(configurationId));
255
256        if (!result.visibleToModule(module))
257            throw new ApplicationRuntimeException(ImplMessages.configurationNotVisible(
258                    configurationId,
259                    module));
260
261        return result;
262    }
263
264    public List getConfiguration(String configurationId, Module module)
265    {
266        ConfigurationPoint point = getConfigurationPoint(configurationId, module);
267
268        return point.getElements();
269    }
270
271    public boolean isConfigurationMappable(String configurationId, Module module)
272    {
273        ConfigurationPoint point = getConfigurationPoint(configurationId, module);
274
275        return point.areElementsMappable();
276    }
277
278    public Map getConfigurationAsMap(String configurationId, Module module)
279    {
280        ConfigurationPoint point = getConfigurationPoint(configurationId, module);
281
282        return point.getElementsAsMap();
283    }
284
285    public String toString()
286    {
287        ToStringBuilder builder = new ToStringBuilder(this);
288
289        builder.append("locale", _locale);
290
291        return builder.toString();
292    }
293
294    public String expandSymbols(String text, Location location)
295    {
296        return _expander.expandSymbols(text, location);
297    }
298
299    public String valueForSymbol(String name)
300    {
301        checkShutdown();
302
303        SymbolSource[] sources = getSymbolSources();
304
305        for (int i = 0; i < sources.length; i++)
306        {
307            String value = sources[i].valueForSymbol(name);
308
309            if (value != null)
310                return value;
311        }
312
313        return null;
314    }
315
316    private synchronized SymbolSource[] getSymbolSources()
317    {
318        if (_variableSources != null)
319            return _variableSources;
320
321        List contributions = getConfiguration(SYMBOL_SOURCES, null);
322
323        Orderer o = new Orderer(LogFactory.getLog(SYMBOL_SOURCES), _errorHandler, ImplMessages
324                .symbolSourceContribution());
325
326        Iterator i = contributions.iterator();
327        while (i.hasNext())
328        {
329            SymbolSourceContribution c = (SymbolSourceContribution) i.next();
330
331            o.add(c, c.getName(), c.getPrecedingNames(), c.getFollowingNames());
332        }
333
334        List sources = o.getOrderedObjects();
335
336        int count = sources.size();
337
338        _variableSources = new SymbolSource[count];
339
340        for (int j = 0; j < count; j++)
341        {
342            SymbolSourceContribution c = (SymbolSourceContribution) sources.get(j);
343            _variableSources[j] = c.getSource();
344        }
345
346        return _variableSources;
347    }
348
349    public void setShutdownCoordinator(ShutdownCoordinator coordinator)
350    {
351        _shutdownCoordinator = coordinator;
352    }
353
354    /**
355     * Invokes {@link ShutdownCoordinator#shutdown()}, then releases the coordinator, modules and
356     * variable sources.
357     */
358    public synchronized void shutdown()
359    {
360        checkShutdown();
361
362        ServiceSerializationHelper.setServiceSerializationSupport(null);
363
364        // Allow service implementations and such to shutdown.
365
366        ShutdownCoordinator coordinatorService = (ShutdownCoordinator) getService(
367                "hivemind.ShutdownCoordinator",
368                ShutdownCoordinator.class,
369                null);
370
371        coordinatorService.shutdown();
372
373        // TODO: Shoudl this be moved earlier?
374
375        _shutdown = true;
376
377        // Shutdown infrastructure items, such as proxies.
378
379        _shutdownCoordinator.shutdown();
380
381        _servicePoints = null;
382        _servicePointsByInterfaceClassName = null;
383        _configurationPoints = null;
384        _shutdownCoordinator = null;
385        _variableSources = null;
386        _serviceModelFactories = null;
387        _threadEventNotifier = null;
388        _serviceTokens = null;
389
390        // It is believed that the cache held by PropertyUtils can affect application shutdown
391        // and reload in some servlet containers (such as Tomcat); this should clear that up.
392
393        PropertyUtils.clearCache();
394    }
395
396    /**
397     * Technically, this should be a synchronized method, but the _shutdown variable hardly ever
398     * changes, and the consequences are pretty minimal. See HIVEMIND-104.
399     */
400
401    private void checkShutdown()
402    {
403        if (_shutdown)
404            throw new ApplicationRuntimeException(HiveMindMessages.registryShutdown());
405    }
406
407    private void checkStarted()
408    {
409        if (_started)
410            throw new IllegalStateException(ImplMessages.registryAlreadyStarted());
411    }
412
413    /**
414     * Starts up the Registry after all service and configuration points have been defined. This
415     * locks down the Registry so that no further extension points may be added. This method may
416     * only be invoked once.
417     * <p>
418     * This instance is stored into
419     * {@link ServiceSerializationHelper#setServiceSerializationSupport(ServiceSerializationSupport)}.
420     * This may cause errors (and incorrect behavior) if multiple Registries exist in a single JVM.
421     * <p>
422     * In addition, the service <code>hivemind.Startup</code> is obtained and <code>run()</code>
423     * is invoked on it. This allows additional startup, provided in the
424     * <code>hivemind.Startup</code> configuration point, to be executed.
425     */
426    public void startup()
427    {
428        checkStarted();
429
430        ServiceSerializationHelper.setServiceSerializationSupport(this);
431
432        _started = true;
433
434        Runnable startup = (Runnable) getService("hivemind.Startup", Runnable.class, null);
435
436        startup.run();
437    }
438
439    public synchronized ServiceModelFactory getServiceModelFactory(String name)
440    {
441        if (_serviceModelFactories == null)
442            readServiceModelFactories();
443
444        ServiceModelFactory result = (ServiceModelFactory) _serviceModelFactories.get(name);
445
446        if (result == null)
447            throw new ApplicationRuntimeException(ImplMessages.unknownServiceModel(name));
448
449        return result;
450    }
451
452    private void readServiceModelFactories()
453    {
454        List l = getConfiguration("hivemind.ServiceModels", null);
455
456        _serviceModelFactories = new HashMap();
457
458        Iterator i = l.iterator();
459
460        while (i.hasNext())
461        {
462            ServiceModelContribution smc = (ServiceModelContribution) i.next();
463
464            String name = smc.getName();
465
466            _serviceModelFactories.put(name, smc.getFactory());
467        }
468    }
469
470    public synchronized void cleanupThread()
471    {
472        if (_threadEventNotifier == null)
473            _threadEventNotifier = (ThreadEventNotifier) getService(
474                    "hivemind.ThreadEventNotifier",
475                    ThreadEventNotifier.class,
476                    null);
477
478        _threadEventNotifier.fireThreadCleanup();
479    }
480
481    public boolean containsConfiguration(String configurationId, Module module)
482    {
483        checkShutdown();
484
485        ConfigurationPoint result = (ConfigurationPoint) _configurationPoints.get(configurationId);
486
487        return result != null && result.visibleToModule(module);
488    }
489
490    public boolean containsService(Class serviceInterface, Module module)
491    {
492        checkShutdown();
493
494        String key = serviceInterface.getName();
495
496        List servicePoints = (List) _servicePointsByInterfaceClassName.get(key);
497
498        if (servicePoints == null)
499            return false;
500
501        int count = 0;
502
503        Iterator i = servicePoints.iterator();
504        while (i.hasNext())
505        {
506            ServicePoint point = (ServicePoint) i.next();
507
508            if (point.visibleToModule(module))
509                count++;
510        }
511
512        return count == 1;
513    }
514
515    public boolean containsService(String serviceId, Class serviceInterface, Module module)
516    {
517        checkShutdown();
518
519        ServicePoint point = (ServicePoint) _servicePoints.get(serviceId);
520
521        if (point == null)
522            return false;
523
524        return point.visibleToModule(module)
525                && point.getServiceInterface().equals(serviceInterface);
526    }
527
528    public ErrorHandler getErrorHander()
529    {
530        return _errorHandler;
531    }
532
533    public Translator getTranslator(String constructor)
534    {
535        return _translatorManager.getTranslator(constructor);
536    }
537
538    public Object getServiceFromToken(ServiceToken token)
539    {
540        Defense.notNull(token, "token");
541
542        checkShutdown();
543
544        String serviceId = token.getServiceId();
545
546        ServicePoint sp = (ServicePoint) _servicePoints.get(serviceId);
547
548        return sp.getService(Object.class);
549    }
550
551    public synchronized ServiceToken getServiceTokenForService(String serviceId)
552    {
553        Defense.notNull(serviceId, "serviceId");
554
555        checkShutdown();
556
557        if (_serviceTokens == null)
558            _serviceTokens = new HashMap();
559
560        ServiceToken result = (ServiceToken) _serviceTokens.get(serviceId);
561
562        if (result == null)
563        {
564            result = new ServiceToken(serviceId);
565            _serviceTokens.put(serviceId, result);
566        }
567
568        return result;
569    }
570
571    /**
572     * Sets the current RI up as the ServiceSerializationSupport. Any service proxy tokens that are
573     * de-serialized will find their proxies within this Registry.
574     * 
575     * @since 1.1
576     */
577
578    public void setupThread()
579    {
580        ServiceSerializationHelper.setServiceSerializationSupport(this);
581    }
582
583    public Module getModule(String moduleId)
584    {
585        for (Iterator i = _servicePoints.values().iterator(); i.hasNext();)
586        {
587            final ServicePoint servicePoint = (ServicePoint) i.next();
588
589            if (servicePoint.getModule().getModuleId().equals(moduleId))
590            {
591                return servicePoint.getModule();
592            }
593        }
594        return null;
595    }
596
597    /*
598     * (non-Javadoc)
599     * 
600     * @see org.apache.hivemind.internal.RegistryInfrastructure#getServiceIds(java.lang.Class)
601     */
602    public List getServiceIds(Class serviceInterface)
603    {
604        final List serviceIds = new LinkedList();
605        if( serviceInterface == null )
606        {
607            return serviceIds;
608        }
609        for (Iterator i = _servicePoints.values().iterator(); i.hasNext();)
610        {
611            final ServicePoint servicePoint = (ServicePoint) i.next();
612
613            if (serviceInterface.getName().equals( servicePoint.getServiceInterfaceClassName() )
614                    && servicePoint.visibleToModule(null))
615            {
616                serviceIds.add(servicePoint.getExtensionPointId());
617            }
618
619        }
620        return serviceIds;
621    }
622}