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