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