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