001    /*
002      GRANITE DATA SERVICES
003      Copyright (C) 2011 GRANITE DATA SERVICES S.A.S.
004    
005      This file is part of Granite Data Services.
006    
007      Granite Data Services is free software; you can redistribute it and/or modify
008      it under the terms of the GNU Library General Public License as published by
009      the Free Software Foundation; either version 2 of the License, or (at your
010      option) any later version.
011    
012      Granite Data Services is distributed in the hope that it will be useful, but
013      WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014      FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015      for more details.
016    
017      You should have received a copy of the GNU Library General Public License
018      along with this library; if not, see <http://www.gnu.org/licenses/>.
019    */
020    
021    package org.granite.config;
022    
023    import java.io.ByteArrayInputStream;
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.io.ObjectInput;
027    import java.io.ObjectOutput;
028    import java.io.OutputStream;
029    import java.lang.annotation.Annotation;
030    import java.lang.reflect.Constructor;
031    import java.lang.reflect.Modifier;
032    import java.util.ArrayList;
033    import java.util.HashMap;
034    import java.util.List;
035    import java.util.Map;
036    import java.util.Properties;
037    import java.util.Set;
038    import java.util.concurrent.ConcurrentHashMap;
039    
040    import org.granite.config.api.Configuration;
041    import org.granite.logging.Logger;
042    import org.granite.messaging.amf.io.AMF3Deserializer;
043    import org.granite.messaging.amf.io.AMF3DeserializerSecurizer;
044    import org.granite.messaging.amf.io.AMF3Serializer;
045    import org.granite.messaging.amf.io.convert.Converter;
046    import org.granite.messaging.amf.io.convert.Converters;
047    import org.granite.messaging.amf.io.util.ActionScriptClassDescriptor;
048    import org.granite.messaging.amf.io.util.ClassGetter;
049    import org.granite.messaging.amf.io.util.DefaultClassGetter;
050    import org.granite.messaging.amf.io.util.JavaClassDescriptor;
051    import org.granite.messaging.amf.io.util.externalizer.Externalizer;
052    import org.granite.messaging.amf.process.AMF3MessageInterceptor;
053    import org.granite.messaging.service.DefaultMethodMatcher;
054    import org.granite.messaging.service.ExceptionConverter;
055    import org.granite.messaging.service.MethodMatcher;
056    import org.granite.messaging.service.ServiceInvocationListener;
057    import org.granite.messaging.service.security.SecurityService;
058    import org.granite.messaging.service.tide.TideComponentMatcher;
059    import org.granite.scan.ScannedItem;
060    import org.granite.scan.ScannedItemHandler;
061    import org.granite.scan.Scanner;
062    import org.granite.scan.ScannerFactory;
063    import org.granite.util.ClassUtil;
064    import org.granite.util.StreamUtil;
065    import org.granite.util.XMap;
066    import org.xml.sax.EntityResolver;
067    import org.xml.sax.InputSource;
068    import org.xml.sax.SAXException;
069    
070    /**
071     * @author Franck WOLFF
072     */
073    public class GraniteConfig implements ScannedItemHandler {
074    
075        ///////////////////////////////////////////////////////////////////////////
076        // Static fields.
077    
078        private static final Logger log = Logger.getLogger(GraniteConfig.class);
079        
080        private static final String GRANITE_CONFIG_PUBLIC_ID = "-//Granite Data Services//DTD granite-config internal//EN";
081        private static final String GRANITE_CONFIG_PROPERTIES = "META-INF/granite-config.properties";
082    
083        final ExternalizerFactory EXTERNALIZER_FACTORY = new ExternalizerFactory();
084        final ActionScriptClassDescriptorFactory ASC_DESCRIPTOR_FACTORY = new ActionScriptClassDescriptorFactory();
085        final JavaClassDescriptorFactory JC_DESCRIPTOR_FACTORY = new JavaClassDescriptorFactory();
086        final TideComponentMatcherFactory TIDE_COMPONENT_MATCHER_FACTORY = new TideComponentMatcherFactory();
087    
088        ///////////////////////////////////////////////////////////////////////////
089        // Instance fields.
090    
091        // Should we scan classpath for auto-configured services/externalizers?
092        private boolean scan = false;
093        
094        private String MBeanContextName = null;
095    
096        // Custom AMF3 (De)Serializer configuration.
097        private Constructor<AMF3Serializer> amf3SerializerConstructor = null;
098        private Constructor<AMF3Deserializer> amf3DeserializerConstructor = null;
099        
100        private AMF3DeserializerSecurizer amf3DeserializerSecurizer = null;
101    
102        // Custom AMF3 message interceptor configuration.
103        private AMF3MessageInterceptor amf3MessageInterceptor = null;
104    
105        // Converters configuration.
106        private List<Class<? extends Converter>> converterClasses = new ArrayList<Class<? extends Converter>>();
107        private Converters converters = null;
108    
109        // MethodMatcher configuration.
110        private MethodMatcher methodMatcher = new DefaultMethodMatcher();
111    
112        // Invocation listener configuration.
113        private ServiceInvocationListener invocationListener = null;
114    
115        // Instantiators configuration.
116        private final Map<String, String> instantiators = new HashMap<String, String>();
117    
118        // Class getter configuration.
119        private ClassGetter classGetter = new DefaultClassGetter();
120        private boolean classGetterSet = false;
121    
122        // Externalizers configuration.
123        private XMap externalizersConfiguration = null;
124        private final List<Externalizer> scannedExternalizers = new ArrayList<Externalizer>();
125        private final ConcurrentHashMap<String, Externalizer> externalizersByType
126            = new ConcurrentHashMap<String, Externalizer>();
127        private final Map<String, String> externalizersByInstanceOf = new HashMap<String, String>();
128        private final Map<String, String> externalizersByAnnotatedWith = new HashMap<String, String>();
129    
130        // Java descriptors configuration.
131        private final ConcurrentHashMap<String, Class<? extends JavaClassDescriptor>> javaDescriptorsByType
132            = new ConcurrentHashMap<String, Class<? extends JavaClassDescriptor>>();
133        private final Map<String, String> javaDescriptorsByInstanceOf = new HashMap<String, String>();
134    
135        // AS3 descriptors configuration.
136        private final ConcurrentHashMap<String, Class<? extends ActionScriptClassDescriptor>> as3DescriptorsByType
137            = new ConcurrentHashMap<String, Class<? extends ActionScriptClassDescriptor>>();
138        private final Map<String, String> as3DescriptorsByInstanceOf = new HashMap<String, String>();
139        
140        // Exception converters
141        private final List<ExceptionConverter> exceptionConverters = new ArrayList<ExceptionConverter>();
142    
143        // Tide-enabled Components configuration.
144        private final ConcurrentHashMap<String, Object[]> enabledTideComponentsByName = new ConcurrentHashMap<String, Object[]>();
145        private final ConcurrentHashMap<String, Object[]> disabledTideComponentsByName = new ConcurrentHashMap<String, Object[]>();
146        private final List<TideComponentMatcher> tideComponentMatchers = new ArrayList<TideComponentMatcher>();
147    
148        // Security service configuration.
149        private SecurityService securityService = null;
150    
151        // MessageSelector configuration.
152        private Constructor<?> messageSelectorConstructor;
153        
154        // Gravity configuration.
155        private XMap gravityConfig;
156    
157        ///////////////////////////////////////////////////////////////////////////
158        // Constructor.
159    
160        public GraniteConfig(String stdConfig, InputStream customConfigIs, Configuration configuration, String MBeanContextName) throws IOException, SAXException {
161            try {
162                amf3SerializerConstructor = ClassUtil.getConstructor(AMF3Serializer.class, new Class<?>[]{OutputStream.class});
163                amf3DeserializerConstructor = ClassUtil.getConstructor(AMF3Deserializer.class, new Class<?>[]{InputStream.class});
164            } catch (Exception e) {
165                throw new GraniteConfigException("Could not get constructor for AMF3 (de)serializers", e);
166            }
167            
168            this.MBeanContextName = MBeanContextName;
169            
170            ClassLoader loader = GraniteConfig.class.getClassLoader();
171            
172            final ByteArrayInputStream dtd = StreamUtil.getResourceAsStream("org/granite/config/granite-config.dtd", loader);
173            final EntityResolver resolver = new EntityResolver() {
174                public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
175                    if (GRANITE_CONFIG_PUBLIC_ID.equals(publicId)) {
176                        dtd.reset();
177                        InputSource source = new InputSource(dtd);
178                        source.setPublicId(publicId);
179                        return source;
180                    }
181                    return null;
182                }
183            };
184    
185            // Load standard config.
186            InputStream is = null;
187            try {
188                is = StreamUtil.getResourceAsStream("org/granite/config/granite-config.xml", loader);
189                XMap doc = new XMap(is, resolver);
190                forElement(doc, false, null);
191            } finally {
192                if (is != null)
193                    is.close();
194            }
195            
196            if (stdConfig != null) {
197                try {
198                    is = StreamUtil.getResourceAsStream(stdConfig, loader);
199                    XMap doc = new XMap(is, resolver);
200                    forElement(doc, false, null);
201                } finally {
202                    if (is != null)
203                        is.close();
204                }
205            }
206    
207            // Load custom config (override).
208            if (customConfigIs != null) {
209                    XMap doc = new XMap(customConfigIs, resolver);
210                forElement(doc, true, configuration != null ? configuration.getGraniteConfigProperties() : null);
211            }
212            
213            if (amf3DeserializerSecurizer == null)
214                    log.warn("You should configure a deserializer securizer in your granite-config.xml file in order to prevent potential security exploits!");
215        }
216    
217        
218        ///////////////////////////////////////////////////////////////////////////
219        // Classpath scan initialization.
220        
221        private void scanConfig(String graniteConfigProperties) {
222            //if config overriding exists
223            Scanner scanner = ScannerFactory.createScanner(this, graniteConfigProperties != null ? graniteConfigProperties : GRANITE_CONFIG_PROPERTIES);
224            try {
225                scanner.scan();
226            } catch (Exception e) {
227                log.error(e, "Could not scan classpath for configuration");
228            }
229        }
230    
231        public boolean handleMarkerItem(ScannedItem item) {
232            try {
233                return handleProperties(item.loadAsProperties());
234            } catch (Exception e) {
235                log.error(e, "Could not load properties: %s", item);
236            }
237            return true;
238        }
239    
240        public void handleScannedItem(ScannedItem item) {
241            if ("class".equals(item.getExtension()) && item.getName().indexOf('$') == -1) {
242                try {
243                    handleClass(item.loadAsClass());
244                } catch (NoClassDefFoundError e) {
245                    // Ignore errors with Tide classes depending on Gravity
246                } catch (LinkageError e) {
247                    // Ignore errors with GraniteDS/Hibernate classes depending on Hibernate 3 when using Hibernate 4
248                } catch (Throwable t) {
249                    log.error(t, "Could not load class: %s", item);
250                }
251            }
252        }
253    
254        private boolean handleProperties(Properties properties) {
255            if (properties.getProperty("dependsOn") != null) {
256                    String dependsOn = properties.getProperty("dependsOn");
257                    try {
258                            ClassUtil.forName(dependsOn);
259                    }
260                    catch (ClassNotFoundException e) {
261                            // Class not found, skip scan for this package
262                            return true;
263                    }
264            }
265            
266            String classGetterName = properties.getProperty("classGetter");
267            if (!classGetterSet && classGetterName != null) {
268                try {
269                    classGetter = ClassUtil.newInstance(classGetterName, ClassGetter.class);
270                } catch (Throwable t) {
271                    log.error(t, "Could not create instance of: %s", classGetterName);
272                }
273            }
274    
275            String amf3MessageInterceptorName = properties.getProperty("amf3MessageInterceptor");
276            if (amf3MessageInterceptor == null && amf3MessageInterceptorName != null) {
277                try {
278                    amf3MessageInterceptor = ClassUtil.newInstance(amf3MessageInterceptorName, AMF3MessageInterceptor.class);
279                } catch (Throwable t) {
280                    log.error(t, "Could not create instance of: %s", amf3MessageInterceptorName);
281                }
282            }
283            
284            for (Map.Entry<?, ?> me : properties.entrySet()) {
285                if (me.getKey().toString().startsWith("converter.")) {
286                    String converterName = me.getValue().toString();
287                    try {
288                        converterClasses.add(ClassUtil.forName(converterName, Converter.class));
289                    } catch (Exception e) {
290                        throw new GraniteConfigException("Could not get converter class for: " + converterName, e);
291                    }
292                }
293            }
294            
295            return false;
296        }
297    
298        private void handleClass(Class<?> clazz) {
299            if (!clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers())) {
300                if (Externalizer.class.isAssignableFrom(clazz)) {
301                    try {
302                        scannedExternalizers.add(ClassUtil.newInstance(clazz, Externalizer.class));
303                    } catch (Exception e) {
304                        log.error(e, "Could not create new instance of: %s", clazz);
305                    }
306                }
307                
308                if (ExceptionConverter.class.isAssignableFrom(clazz)) {
309                    try {
310                        exceptionConverters.add(ClassUtil.newInstance(clazz, ExceptionConverter.class));
311                    } catch (Exception e) {
312                            if (!clazz.getName().equals("org.granite.tide.hibernate.HibernateValidatorExceptionConverter")) // GDS-582
313                                    log.error(e, "Could not create new instance of: %s", clazz);
314                    }
315                }
316            }
317        }
318    
319        ///////////////////////////////////////////////////////////////////////////
320        // Property getters.
321    
322        public boolean getScan() {
323            return scan;
324        }
325        
326        public boolean isRegisterMBeans() {
327            return MBeanContextName != null;
328        }
329        
330        public String getMBeanContextName() {
331            return MBeanContextName;
332        }
333    
334        
335            public ObjectOutput newAMF3Serializer(OutputStream out) {
336            try {
337                return amf3SerializerConstructor.newInstance(new Object[]{out});
338            } catch (Exception e) {
339                throw new GraniteConfigException("Could not create serializer instance with: " + amf3SerializerConstructor, e);
340            }
341        }
342            
343            public Constructor<?> getAmf3SerializerConstructor() {
344                    return amf3SerializerConstructor;
345            }
346    
347        public ObjectInput newAMF3Deserializer(InputStream in) {
348            try {
349                return amf3DeserializerConstructor.newInstance(new Object[]{in});
350            } catch (Exception e) {
351                throw new GraniteConfigException("Could not create deserializer instance with: " + amf3DeserializerConstructor, e);
352            }
353        }
354            
355            public Constructor<?> getAmf3DeserializerConstructor() {
356                    return amf3DeserializerConstructor;
357            }
358    
359        public AMF3DeserializerSecurizer getAmf3DeserializerSecurizer() {
360                    return amf3DeserializerSecurizer;
361            }
362            public void setAmf3DeserializerSecurizer(
363                            AMF3DeserializerSecurizer amf3DeserializerSecurizer) {
364                    this.amf3DeserializerSecurizer = amf3DeserializerSecurizer;
365            }
366    
367            public AMF3MessageInterceptor getAmf3MessageInterceptor() {
368            return amf3MessageInterceptor;
369        }
370        public void setAmf3MessageInterceptor(AMF3MessageInterceptor amf3MessageInterceptor) {
371            this.amf3MessageInterceptor = amf3MessageInterceptor;
372        }
373        
374        public Map<String, String> getInstantiators() {
375            return instantiators;
376        }
377    
378        public Converters getConverters() {
379            return converters;
380        }
381    
382        public MethodMatcher getMethodMatcher() {
383            return methodMatcher;
384        }
385    
386        public ServiceInvocationListener getInvocationListener() {
387            return invocationListener;
388        }
389    
390        public String getInstantiator(String type) {
391            return instantiators.get(type);
392        }
393    
394        public ClassGetter getClassGetter() {
395            return classGetter;
396        }
397    
398        public XMap getExternalizersConfiguration() {
399                    return externalizersConfiguration;
400            }
401    
402            public void setExternalizersConfiguration(XMap externalizersConfiguration) {
403                    this.externalizersConfiguration = externalizersConfiguration;
404            }
405    
406            public Externalizer getExternalizer(String type) {
407            return getElementByType(
408                type,
409                EXTERNALIZER_FACTORY,
410                externalizersByType,
411                externalizersByInstanceOf,
412                externalizersByAnnotatedWith,
413                scannedExternalizers
414            );
415        }
416            
417            public Map<String, Externalizer> getExternalizersByType() {
418                    return externalizersByType;
419            }
420            
421            public Map<String, String> getExternalizersByInstanceOf() {
422                    return externalizersByInstanceOf;
423            }
424            
425            public Map<String, String> getExternalizersByAnnotatedWith() {
426                    return externalizersByAnnotatedWith;
427            }
428            
429            public List<Externalizer> getScannedExternalizers() {
430                    return scannedExternalizers;
431            }
432    
433            
434        public Class<? extends ActionScriptClassDescriptor> getActionScriptDescriptor(String type) {
435            return getElementByType(type, ASC_DESCRIPTOR_FACTORY, as3DescriptorsByType, as3DescriptorsByInstanceOf, null, null);
436        }
437    
438        public Map<String, Class<? extends ActionScriptClassDescriptor>> getAs3DescriptorsByType() {
439            return as3DescriptorsByType;
440        }
441    
442        public Map<String, String> getAs3DescriptorsByInstanceOf() {
443            return as3DescriptorsByInstanceOf;
444        }
445        
446        
447        public Class<? extends JavaClassDescriptor> getJavaDescriptor(String type) {
448            return getElementByType(type, JC_DESCRIPTOR_FACTORY, javaDescriptorsByType, javaDescriptorsByInstanceOf, null, null);
449        }
450    
451        public Map<String, Class<? extends JavaClassDescriptor>> getJavaDescriptorsByType() {
452            return javaDescriptorsByType;
453        }
454    
455        public Map<String, String> getJavaDescriptorsByInstanceOf() {
456            return javaDescriptorsByInstanceOf;
457        }    
458        
459        
460        public boolean isComponentTideEnabled(String componentName, Set<Class<?>> componentClasses, Object instance) {
461            return TideComponentMatcherFactory.isComponentTideEnabled(enabledTideComponentsByName, tideComponentMatchers, componentName, componentClasses, instance);
462        }
463        
464        public boolean isComponentTideDisabled(String componentName, Set<Class<?>> componentClasses, Object instance) {
465            return TideComponentMatcherFactory.isComponentTideDisabled(disabledTideComponentsByName, tideComponentMatchers, componentName, componentClasses, instance);
466        }
467        
468        
469        public List<ExceptionConverter> getExceptionConverters() {
470            return exceptionConverters;
471        }
472        
473        public void registerExceptionConverter(Class<? extends ExceptionConverter> exceptionConverterClass) {
474            registerExceptionConverter(exceptionConverterClass, false);
475        }
476        public void registerExceptionConverter(Class<? extends ExceptionConverter> exceptionConverterClass, boolean first) {
477            for (ExceptionConverter ec : exceptionConverters) {
478                    if (ec.getClass() == exceptionConverterClass)
479                            return;
480            }
481                    try {
482                            ExceptionConverter exceptionConverter = ClassUtil.newInstance(exceptionConverterClass, ExceptionConverter.class);
483                    if (first)
484                        exceptionConverters.add(0, exceptionConverter);
485                    else
486                        exceptionConverters.add(exceptionConverter);
487                    } 
488                    catch (Exception e) {
489                            log.error(e, "Could not instantiate exception converter: %s", exceptionConverterClass);
490                    }
491        }
492        
493        public void registerExceptionConverter(ExceptionConverter exceptionConverter, boolean first) {
494            for (ExceptionConverter ec : exceptionConverters) {
495                    if (ec.getClass() == exceptionConverter.getClass())
496                            return;
497            }
498            if (first)
499                exceptionConverters.add(0, exceptionConverter);
500            else
501                exceptionConverters.add(exceptionConverter);
502        }
503    
504        public boolean hasSecurityService() {
505            return securityService != null;
506        }
507    
508        public SecurityService getSecurityService() {
509            return securityService;
510        }
511        
512        public List<TideComponentMatcher> getTideComponentMatchers() {
513            return tideComponentMatchers;
514        }
515        
516        public Map<String, Object[]> getEnabledTideComponentsByName() {
517            return enabledTideComponentsByName;
518        }
519        
520        public Map<String, Object[]> getDisabledTideComponentsByName() {
521            return disabledTideComponentsByName;
522        }
523        
524            
525            public XMap getGravityConfig() {
526                    return gravityConfig;
527            }
528    
529        public Constructor<?> getMessageSelectorConstructor() {
530            return messageSelectorConstructor;
531        }
532        public Externalizer setExternalizersByType(String type, String externalizerType) {
533            return externalizersByType.put(type, EXTERNALIZER_FACTORY.getInstance(externalizerType, this));
534        }
535    
536        public String putExternalizersByInstanceOf(String instanceOf, String externalizerType) {
537            return externalizersByInstanceOf.put(instanceOf, externalizerType);
538        }
539    
540        public String putExternalizersByAnnotatedWith(String annotatedWith, String externalizerType) {
541            return externalizersByAnnotatedWith.put(annotatedWith, externalizerType);
542        }
543    
544        ///////////////////////////////////////////////////////////////////////////
545        // Static GraniteConfig loading helpers.
546    
547        private void forElement(XMap element, boolean custom, String graniteConfigProperties) {
548            String scan = element.get("@scan");
549    
550            this.scan = Boolean.TRUE.toString().equals(scan);
551    
552            loadCustomAMF3Serializer(element, custom);
553            loadCustomAMF3DeserializerSecurizer(element, custom);
554            loadCustomAMF3MessageInterceptor(element, custom);
555            loadCustomConverters(element, custom);
556            loadCustomMethodMatcher(element, custom);
557            loadCustomInvocationListener(element, custom);
558            loadCustomInstantiators(element, custom);
559            loadCustomClassGetter(element, custom);
560            loadCustomExternalizers(element, custom);
561            loadCustomDescriptors(element, custom);
562            loadCustomExceptionConverters(element, custom);
563            loadCustomTideComponents(element, custom);
564            loadCustomSecurity(element, custom);
565            loadCustomMessageSelector(element, custom);
566            loadCustomGravity(element, custom);
567    
568            if (this.scan)
569                scanConfig(graniteConfigProperties);
570    
571            finishCustomConverters(custom);
572        }
573    
574        private void loadCustomAMF3Serializer(XMap element, boolean custom) {
575            XMap amf3Serializer = element.getOne("amf3-serializer");
576            if (amf3Serializer != null) {
577                String type = amf3Serializer.get("@type");
578                try {
579                    Class<AMF3Serializer> amf3SerializerClass = ClassUtil.forName(type, AMF3Serializer.class);
580                    amf3SerializerConstructor = ClassUtil.getConstructor(amf3SerializerClass, new Class<?>[]{OutputStream.class});
581                } catch (Exception e) {
582                    throw new GraniteConfigException("Could not get constructor for AMF3 serializer: " + type, e);
583                }
584            }
585    
586            XMap amf3Deserializer = element.getOne("amf3-deserializer");
587            if (amf3Deserializer != null) {
588                String type = amf3Deserializer.get("@type");
589                try {
590                    Class<AMF3Deserializer> amf3DeserializerClass = ClassUtil.forName(type, AMF3Deserializer.class);
591                    amf3DeserializerConstructor = ClassUtil.getConstructor(amf3DeserializerClass, new Class<?>[]{InputStream.class});
592                } catch (Exception e) {
593                    throw new GraniteConfigException("Could not get constructor for AMF3 deserializer: " + type, e);
594                }
595            }
596        }
597    
598        private void loadCustomAMF3DeserializerSecurizer(XMap element, boolean custom) {
599            XMap securizer = element.getOne("amf3-deserializer-securizer");
600            if (securizer != null) {
601                String type = securizer.get("@type");
602                try {
603                    amf3DeserializerSecurizer = (AMF3DeserializerSecurizer)ClassUtil.newInstance(type);
604                } catch (Exception e) {
605                    throw new GraniteConfigException("Could not construct amf3 deserializer securizer: " + type, e);
606                }
607                String param = securizer.get("@param");
608                try {
609                    amf3DeserializerSecurizer.setParam(param);
610                } catch (Exception e) {
611                    throw new GraniteConfigException("Could not set param of amf3 deserializer securizer: " + type + ", param: " + param, e);
612                }
613            }
614        }
615    
616        private void loadCustomAMF3MessageInterceptor(XMap element, boolean custom) {
617            XMap interceptor = element.getOne("amf3-message-interceptor");
618            if (interceptor != null) {
619                String type = interceptor.get("@type");
620                try {
621                    amf3MessageInterceptor = (AMF3MessageInterceptor)ClassUtil.newInstance(type);
622                } catch (Exception e) {
623                    throw new GraniteConfigException("Could not construct amf3 message interceptor: " + type, e);
624                }
625            }
626        }
627    
628        private void loadCustomConverters(XMap element, boolean custom) {
629            XMap converters = element.getOne("converters");
630            if (converters != null) {
631                // Should we override standard config converters?
632                String override = converters.get("@override");
633                if (Boolean.TRUE.toString().equals(override))
634                    converterClasses.clear();
635    
636                int i = 0;
637                for (XMap converter : converters.getAll("converter")) {
638                    String type = converter.get("@type");
639                    try {
640                        // For custom config, shifts any standard converters to the end of the list...
641                        converterClasses.add(i++, ClassUtil.forName(type, Converter.class));
642                    } catch (Exception e) {
643                        throw new GraniteConfigException("Could not get converter class for: " + type, e);
644                    }
645                }
646            }
647        }
648        
649        private void finishCustomConverters(boolean custom) {
650            try {
651                converters = new Converters(converterClasses);
652            } catch (Exception e) {
653                throw new GraniteConfigException("Could not construct new Converters instance", e);
654            }
655            
656            // Cleanup...
657            if (custom)
658                converterClasses = null;
659        }
660    
661        private void loadCustomMethodMatcher(XMap element, boolean custom) {
662            XMap methodMatcher = element.getOne("method-matcher");
663            if (methodMatcher != null) {
664                String type = methodMatcher.get("@type");
665                try {
666                    this.methodMatcher = (MethodMatcher)ClassUtil.newInstance(type);
667                } catch (Exception e) {
668                    throw new GraniteConfigException("Could not construct method matcher: " + type, e);
669                }
670            }
671        }
672    
673        private void loadCustomInvocationListener(XMap element, boolean custom) {
674            XMap invocationListener = element.getOne("invocation-listener");
675            if (invocationListener != null) {
676                String type = invocationListener.get("@type");
677                try {
678                    this.invocationListener = (ServiceInvocationListener)ClassUtil.newInstance(type);
679                } catch (Exception e) {
680                    throw new GraniteConfigException("Could not instantiate ServiceInvocationListener: " + type, e);
681                }
682            }
683        }
684    
685        private void loadCustomInstantiators(XMap element, boolean custom) {
686            XMap instantiators = element.getOne("instantiators");
687            if (instantiators != null) {
688                for (XMap instantiator : instantiators.getAll("instantiator"))
689                    this.instantiators.put(instantiator.get("@type"), instantiator.get("."));
690            }
691        }
692    
693        private void loadCustomClassGetter(XMap element, boolean custom) {
694            XMap classGetter = element.getOne("class-getter");
695            if (classGetter != null) {
696                String type = classGetter.get("@type");
697                try {
698                    this.classGetter = (ClassGetter)ClassUtil.newInstance(type);
699                    classGetterSet = true;
700                } catch (Exception e) {
701                    throw new GraniteConfigException("Could not instantiate ClassGetter: " + type, e);
702                }
703            }
704        }
705    
706        private void loadCustomExternalizers(XMap element, boolean custom) {
707            externalizersConfiguration = element.getOne("externalizers/configuration");
708            
709            for (XMap externalizer : element.getAll("externalizers/externalizer")) {
710                String externalizerType = externalizer.get("@type");
711    
712                for (XMap include : externalizer.getAll("include")) {
713                    String type = include.get("@type");
714                    if (type != null)
715                        externalizersByType.put(type, EXTERNALIZER_FACTORY.getInstance(externalizerType, this));
716                    else {
717                        String instanceOf = include.get("@instance-of");
718                        if (instanceOf != null)
719                            externalizersByInstanceOf.put(instanceOf, externalizerType);
720                        else {
721                            String annotatedWith = include.get("@annotated-with");
722                            if (annotatedWith == null)
723                                throw new GraniteConfigException(
724                                    "Element 'include' has no attribute 'type', 'instance-of' or 'annotated-with'");
725                            externalizersByAnnotatedWith.put(annotatedWith, externalizerType);
726                        }
727                    }
728                }
729            }
730        }
731    
732        /**
733         * Read custom class descriptors.
734         * Descriptor must have 'type' or 'instanceof' attribute
735         * and one of 'java' or 'as3' attributes specified.
736         */
737        private void loadCustomDescriptors(XMap element, boolean custom) {
738            for (XMap descriptor : element.getAll("descriptors/descriptor")) {
739                String type = descriptor.get("@type");
740                if (type != null) {
741                    String java = descriptor.get("@java");
742                    String as3 = descriptor.get("@as3");
743                    if (java == null && as3 == null)
744                        throw new GraniteConfigException(
745                            "Element 'descriptor' has no attributes 'java' or 'as3'\n" + descriptor
746                        );
747                    if (java != null)
748                        javaDescriptorsByType.put(type, JC_DESCRIPTOR_FACTORY.getInstance(java, this));
749                    if (as3 != null)
750                        as3DescriptorsByType.put(type, ASC_DESCRIPTOR_FACTORY.getInstance(as3, this));
751                } else {
752                    String instanceOf = descriptor.get("@instance-of");
753                    if (instanceOf == null)
754                        throw new GraniteConfigException(
755                            "Element 'descriptor' has no attribute 'type' or 'instance-of'\n" + descriptor
756                        );
757                    String java = descriptor.get("@java");
758                    String as3 = descriptor.get("@as3");
759                    if (java == null && as3 == null) {
760                        throw new GraniteConfigException(
761                            "Element 'descriptor' has no attributes 'java' or 'as3' in:\n" + descriptor
762                        );
763                    }
764                    if (java != null)
765                        javaDescriptorsByInstanceOf.put(instanceOf, java);
766                    if (as3 != null)
767                        as3DescriptorsByInstanceOf.put(instanceOf, as3);
768                }
769            }
770        }
771    
772        /**
773         * Read custom class exception converters
774         * Converter must have 'type' attribute
775         */
776        private void loadCustomExceptionConverters(XMap element, boolean custom) {
777            for (XMap exceptionConverter : element.getAll("exception-converters/exception-converter")) {
778                String type = exceptionConverter.get("@type");
779                ExceptionConverter converter = null;
780                try {
781                    converter = (ExceptionConverter)ClassUtil.newInstance(type);
782                    exceptionConverters.add(converter);
783                } catch (Exception e) {
784                    throw new GraniteConfigException("Could not construct exception converter: " + type, e);
785                }
786            }
787        }
788    
789        private void loadCustomTideComponents(XMap element, boolean custom) {
790            for (XMap component : element.getAll("tide-components/tide-component")) {
791                boolean disabled = Boolean.TRUE.toString().equals(component.get("@disabled"));
792                String type = component.get("@type");
793                if (type != null)
794                    tideComponentMatchers.add(TIDE_COMPONENT_MATCHER_FACTORY.getTypeMatcher(type, disabled));
795                else {
796                    String name = component.get("@name");
797                    if (name != null)
798                        tideComponentMatchers.add(TIDE_COMPONENT_MATCHER_FACTORY.getNameMatcher(name, disabled));
799                    else {
800                        String instanceOf = component.get("@instance-of");
801                        if (instanceOf != null)
802                            tideComponentMatchers.add(TIDE_COMPONENT_MATCHER_FACTORY.getInstanceOfMatcher(instanceOf, disabled));
803                        else {
804                            String annotatedWith = component.get("@annotated-with");
805                            if (annotatedWith == null)
806                                throw new GraniteConfigException(
807                                    "Element 'component' has no attribute 'type', 'name', 'instance-of' or 'annotated-with'");
808                            tideComponentMatchers.add(TIDE_COMPONENT_MATCHER_FACTORY.getAnnotatedWithMatcher(annotatedWith, disabled));
809                        }
810                    }
811                }
812            }
813        }
814    
815        private void loadCustomSecurity(XMap element, boolean custom) {
816            XMap security = element.getOne("security");
817            if (security != null) {
818                String type = security.get("@type");
819                try {
820                    securityService = (SecurityService)ClassUtil.newInstance(type);
821                } catch (Exception e) {
822                    throw new GraniteConfigException("Could not instantiate SecurityService: " + type, e);
823                }
824    
825                Map<String, String> params = new HashMap<String, String>();
826                for (XMap param : security.getAll("param")) {
827                    String name = param.get("@name");
828                    String value = param.get("@value");
829                    params.put(name, value);
830                }
831                try {
832                    securityService.configure(params);
833                } catch (Exception e) {
834                    throw new GraniteConfigException("Could not configure SecurityService " + type + " with: " + params, e);
835                }
836            }
837        }
838        
839        public void setSecurityService(SecurityService securityService) {
840            this.securityService = securityService;
841        }
842    
843        private void loadCustomMessageSelector(XMap element, boolean custom) {
844            XMap selector = element.getOne("message-selector");
845            if (selector != null) {
846                String type = selector.get("@type");
847                try {
848                    messageSelectorConstructor = ClassUtil.getConstructor(type, new Class<?>[]{ String.class });
849                } catch (Exception e) {
850                    throw new GraniteConfigException("Could not construct message selector: " + type, e);
851                }
852            }
853        }
854    
855        private void loadCustomGravity(XMap element, boolean custom) {
856            gravityConfig = element.getOne("gravity");
857        }
858    
859        ///////////////////////////////////////////////////////////////////////////
860        // Other helpers.
861    
862        private <T> T getElementByType(
863            String type,
864            ConfigurableFactory<T> factory,
865            ConcurrentHashMap<String, T> elementsByType,
866            Map<String, String> elementsByInstanceOf,
867            Map<String, String> elementsByAnnotatedWith,
868            List<T> scannedConfigurables) {
869    
870            // This NULL object is a Java null placeholder: ConcurrentHashMap doesn't allow
871            // null values...
872            final T NULL = factory.getNullInstance();
873    
874            T element = elementsByType.get(type);
875            if (element != null)
876                return (NULL == element ? null : element);
877            element = NULL;
878    
879            Class<?> typeClass = null;
880            try {
881                typeClass = ClassUtil.forName(type);
882            } catch (Exception e) {
883                throw new GraniteConfigException("Could not load class: " + type, e);
884            }
885    
886            if (elementsByAnnotatedWith != null && NULL == element) {
887                for (Map.Entry<String, String> entry : elementsByAnnotatedWith.entrySet()) {
888                    String annotation = entry.getKey();
889                    try {
890                        Class<Annotation> annotationClass = ClassUtil.forName(annotation, Annotation.class);
891                        if (typeClass.isAnnotationPresent(annotationClass)) {
892                            element = factory.getInstance(entry.getValue(), this);
893                            break;
894                        }
895                    } catch (Exception e) {
896                        throw new GraniteConfigException("Could not load class: " + annotation, e);
897                    }
898                }
899            }
900    
901            if (elementsByInstanceOf != null && NULL == element) {
902                    for (Map.Entry<String, String> entry : elementsByInstanceOf.entrySet()) {
903                        String instanceOf = entry.getKey();
904                        try {
905                            Class<?> instanceOfClass = ClassUtil.forName(instanceOf);
906                            if (instanceOfClass.isAssignableFrom(typeClass)) {
907                                element = factory.getInstance(entry.getValue(), this);
908                                break;
909                            }
910                        } catch (Exception e) {
911                            throw new GraniteConfigException("Could not load class: " + instanceOf, e);
912                        }
913                    }
914            }
915    
916            if (NULL == element)
917                element = factory.getInstanceForBean(scannedConfigurables, typeClass, this);
918    
919            T previous = elementsByType.putIfAbsent(type, element);
920            if (previous != null)
921                element = previous;
922    
923            return (NULL == element ? null : element);
924        }
925    }