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 */
022package org.granite.config;
023
024import java.io.ByteArrayInputStream;
025import java.io.Externalizable;
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.ObjectInput;
029import java.io.ObjectOutput;
030import java.io.OutputStream;
031import java.lang.annotation.Annotation;
032import java.lang.reflect.Constructor;
033import java.lang.reflect.Modifier;
034import java.math.BigDecimal;
035import java.math.BigInteger;
036import java.util.ArrayList;
037import java.util.HashMap;
038import java.util.List;
039import java.util.Map;
040import java.util.Properties;
041import java.util.Set;
042import java.util.concurrent.ConcurrentHashMap;
043
044import org.granite.clustering.DistributedDataFactory;
045import org.granite.config.api.Configuration;
046import org.granite.context.GraniteContext;
047import org.granite.logging.Logger;
048import org.granite.messaging.AliasRegistry;
049import org.granite.messaging.DefaultAliasRegistry;
050import org.granite.messaging.amf.io.AMF3Deserializer;
051import org.granite.messaging.amf.io.AMF3DeserializerSecurizer;
052import org.granite.messaging.amf.io.AMF3Serializer;
053import org.granite.messaging.amf.io.convert.Converter;
054import org.granite.messaging.amf.io.convert.Converters;
055import org.granite.messaging.amf.io.util.ActionScriptClassDescriptor;
056import org.granite.messaging.amf.io.util.ClassGetter;
057import org.granite.messaging.amf.io.util.DefaultClassGetter;
058import org.granite.messaging.amf.io.util.JavaClassDescriptor;
059import org.granite.messaging.amf.io.util.externalizer.BigDecimalExternalizer;
060import org.granite.messaging.amf.io.util.externalizer.BigIntegerExternalizer;
061import org.granite.messaging.amf.io.util.externalizer.Externalizer;
062import org.granite.messaging.amf.io.util.externalizer.LongExternalizer;
063import org.granite.messaging.amf.io.util.externalizer.MapExternalizer;
064import org.granite.messaging.amf.process.AMF3MessageInterceptor;
065import org.granite.messaging.jmf.codec.ExtendedObjectCodec;
066import org.granite.messaging.reflect.Reflection;
067import org.granite.messaging.service.DefaultMethodMatcher;
068import org.granite.messaging.service.ExceptionConverter;
069import org.granite.messaging.service.MethodMatcher;
070import org.granite.messaging.service.ServiceInvocationListener;
071import org.granite.messaging.service.security.SecurityService;
072import org.granite.messaging.service.tide.TideComponentMatcher;
073import org.granite.scan.*;
074import org.granite.util.StreamUtil;
075import org.granite.util.TypeUtil;
076import org.granite.util.XMap;
077import org.xml.sax.EntityResolver;
078import org.xml.sax.InputSource;
079import org.xml.sax.SAXException;
080
081/**
082 * @author Franck WOLFF
083 */
084public 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}