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