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.Collections;
018    import java.util.HashMap;
019    import java.util.Iterator;
020    import java.util.LinkedList;
021    import java.util.List;
022    import java.util.Locale;
023    import java.util.Map;
024    
025    import org.apache.commons.logging.LogFactory;
026    import org.apache.hivemind.ApplicationRuntimeException;
027    import org.apache.hivemind.ErrorHandler;
028    import org.apache.hivemind.HiveMindMessages;
029    import org.apache.hivemind.Location;
030    import org.apache.hivemind.ShutdownCoordinator;
031    import org.apache.hivemind.SymbolSource;
032    import org.apache.hivemind.SymbolSourceContribution;
033    import org.apache.hivemind.internal.ConfigurationPoint;
034    import org.apache.hivemind.internal.Module;
035    import org.apache.hivemind.internal.RegistryInfrastructure;
036    import org.apache.hivemind.internal.ServiceModelFactory;
037    import org.apache.hivemind.internal.ServicePoint;
038    import org.apache.hivemind.internal.ser.ServiceSerializationHelper;
039    import org.apache.hivemind.internal.ser.ServiceSerializationSupport;
040    import org.apache.hivemind.internal.ser.ServiceToken;
041    import org.apache.hivemind.order.Orderer;
042    import org.apache.hivemind.schema.Translator;
043    import org.apache.hivemind.service.ThreadEventNotifier;
044    import org.apache.hivemind.util.Defense;
045    import org.apache.hivemind.util.PropertyUtils;
046    import org.apache.hivemind.util.ToStringBuilder;
047    
048    /**
049     * Implementation of {@link RegistryInfrastructure}.
050     * 
051     * @author Howard Lewis Ship
052     */
053    public 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    }