001    /*
002      GRANITE DATA SERVICES
003      Copyright (C) 2011 GRANITE DATA SERVICES S.A.S.
004    
005      This file is part of Granite Data Services.
006    
007      Granite Data Services is free software; you can redistribute it and/or modify
008      it under the terms of the GNU Library General Public License as published by
009      the Free Software Foundation; either version 2 of the License, or (at your
010      option) any later version.
011    
012      Granite Data Services is distributed in the hope that it will be useful, but
013      WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014      FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015      for more details.
016    
017      You should have received a copy of the GNU Library General Public License
018      along with this library; if not, see <http://www.gnu.org/licenses/>.
019    */
020    
021    package org.granite.config;
022    
023    import java.io.ByteArrayInputStream;
024    import java.io.Externalizable;
025    import java.io.IOException;
026    import java.io.InputStream;
027    import java.io.ObjectInput;
028    import java.io.ObjectOutput;
029    import java.io.OutputStream;
030    import java.lang.annotation.Annotation;
031    import java.lang.reflect.Constructor;
032    import java.lang.reflect.Modifier;
033    import java.math.BigDecimal;
034    import java.math.BigInteger;
035    import java.util.ArrayList;
036    import java.util.HashMap;
037    import java.util.List;
038    import java.util.Map;
039    import java.util.Properties;
040    import java.util.Set;
041    import java.util.concurrent.ConcurrentHashMap;
042    
043    import org.granite.clustering.DistributedDataFactory;
044    import org.granite.config.api.Configuration;
045    import org.granite.context.GraniteContext;
046    import org.granite.logging.Logger;
047    import org.granite.messaging.amf.RemoteClass;
048    import org.granite.messaging.amf.io.AMF3Deserializer;
049    import org.granite.messaging.amf.io.AMF3DeserializerSecurizer;
050    import org.granite.messaging.amf.io.AMF3Serializer;
051    import org.granite.messaging.amf.io.convert.Converter;
052    import org.granite.messaging.amf.io.convert.Converters;
053    import org.granite.messaging.amf.io.util.ActionScriptClassDescriptor;
054    import org.granite.messaging.amf.io.util.ClassGetter;
055    import org.granite.messaging.amf.io.util.DefaultClassGetter;
056    import org.granite.messaging.amf.io.util.JavaClassDescriptor;
057    import org.granite.messaging.amf.io.util.externalizer.BigDecimalExternalizer;
058    import org.granite.messaging.amf.io.util.externalizer.BigIntegerExternalizer;
059    import org.granite.messaging.amf.io.util.externalizer.Externalizer;
060    import org.granite.messaging.amf.io.util.externalizer.LongExternalizer;
061    import org.granite.messaging.amf.io.util.externalizer.MapExternalizer;
062    import org.granite.messaging.amf.process.AMF3MessageInterceptor;
063    import org.granite.messaging.service.DefaultMethodMatcher;
064    import org.granite.messaging.service.ExceptionConverter;
065    import org.granite.messaging.service.MethodMatcher;
066    import org.granite.messaging.service.ServiceInvocationListener;
067    import org.granite.messaging.service.security.SecurityService;
068    import org.granite.messaging.service.tide.TideComponentMatcher;
069    import org.granite.scan.ScannedItem;
070    import org.granite.scan.ScannedItemHandler;
071    import org.granite.scan.Scanner;
072    import org.granite.scan.ScannerFactory;
073    import org.granite.util.StreamUtil;
074    import org.granite.util.TypeUtil;
075    import org.granite.util.XMap;
076    import org.xml.sax.EntityResolver;
077    import org.xml.sax.InputSource;
078    import org.xml.sax.SAXException;
079    
080    /**
081     * @author Franck WOLFF
082     */
083    public 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    }