All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.directwebremoting.impl.DefaultConverterManager Maven / Gradle / Ivy

Go to download

DWR is easy Ajax for Java. It makes it simple to call Java code directly from Javascript. It gets rid of almost all the boiler plate code between the web browser and your Java code.

The newest version!
/*
 * Copyright 2005 Joe Walker
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.directwebremoting.impl;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.directwebremoting.ConversionException;
import org.directwebremoting.extend.Converter;
import org.directwebremoting.extend.ConverterManager;
import org.directwebremoting.extend.ErrorOutboundVariable;
import org.directwebremoting.extend.InboundContext;
import org.directwebremoting.extend.InboundVariable;
import org.directwebremoting.extend.NamedConverter;
import org.directwebremoting.extend.NonNestedOutboundVariable;
import org.directwebremoting.extend.OutboundContext;
import org.directwebremoting.extend.OutboundVariable;
import org.directwebremoting.extend.Property;
import org.directwebremoting.extend.RealRawData;
import org.directwebremoting.io.RawData;
import org.directwebremoting.util.ClasspathScanner;
import org.directwebremoting.util.LocalUtil;
import org.directwebremoting.util.Loggers;

/**
 * A class to manage the converter types and the instantiated class name matches.
 * @author Joe Walker [joe at getahead dot ltd dot uk]
 */
public class DefaultConverterManager implements ConverterManager
{
    /* (non-Javadoc)
     *
     * Called only at start-up! It is important that this method only be called at start-up.  Otherwise we
     * will need to modify it so it is thread safe.
     *
     * @see org.directwebremoting.ConverterManager#addConverterType(java.lang.String, java.lang.String)
     */
    public void addConverterType(String id, String className)
    {
        if (!LocalUtil.isJavaIdentifier(id))
        {
            Loggers.STARTUP.error("Illegal identifier: '" + id + "'");
            return;
        }

        Class clazz = LocalUtil.classForName(id, className, Converter.class);
        if (clazz != null)
        {
            Loggers.STARTUP.debug("- adding converter type: " + id + " = " + clazz.getName());
            converterTypes.put(id, clazz);
        }
    }

    /* (non-Javadoc)
     *
     * Called only at start-up! It is important that this method only be called at start-up.  Otherwise we
     * will need to modify it so it is thread safe.
     *
     * @see org.directwebremoting.ConverterManager#addConverter(java.lang.String, java.lang.String, java.util.Map)
     */
    public void addConverter(String match, String type, Map params) throws IllegalArgumentException, InstantiationException, IllegalAccessException
    {
        Class clazz = converterTypes.get(type);
        if (clazz == null)
        {
            log.debug("Probably not an issue: " + match + " is not available so the " + type + " converter will not load. This is only an problem if you wanted to use it.");
            return;
        }

        Converter converter = (Converter) clazz.newInstance();
        LocalUtil.setParams(converter, params, ignore);

        // add the converter for the specified match
        addConverter(match, converter);
    }

    /* (non-Javadoc)
     *
     * Called only at start-up! It is important that this method only be called at start-up.  Otherwise we
     * will need to modify it so it is thread safe.
     *
     * @see org.directwebremoting.ConverterManager#addConverter(java.lang.String, org.directwebremoting.Converter)
     */
    public void addConverter(String match, Converter converter) throws IllegalArgumentException
    {
        if (LocalUtil.hasText(match) && match.contains("*") && !match.startsWith("["))
        {
            boolean recursive = match.endsWith("**");
            String scan = recursive ? match.substring(0, match.length() - 1) : match;

            ClasspathScanner scanner = new ClasspathScanner(scan, recursive);
            for (String clazz : scanner.getClasses())
            {
                // Call ourselves without the wildcard, processed by the else below
                addConverter(clazz, converter);
            }
        }
        else
        {
            // Check that we don't have this one already
            Converter other = convertersByMatch.get(match);
            if (other != null)
            {
                Loggers.STARTUP.warn("Clash of converters for " + match + ". Using " + converter.getClass().getName() + " in place of " + other.getClass().getName());
            }

            Loggers.STARTUP.debug("- adding converter: " + converter.getClass().getSimpleName() + " for " + match);

            converter.setConverterManager(this);

            if (converter instanceof NamedConverter)
            {
                try
                {
                    NamedConverter namedConverter = (NamedConverter) converter;
                    Class javaClass = LocalUtil.classForName(match);
                    setUpClassMapping(namedConverter, javaClass);
                }
                catch (Exception cne)
                {
                    Loggers.STARTUP.warn("Could not load class [" + match + "]. Conversion will try to work upon other runtime information");
                }
            }

            convertersByMatch.put(match, converter);
            converterCache.clear();
        }
    }

    /* (non-Javadoc)
     *
     * Called only at start-up! It is important that this method only be called at start-up.  Otherwise we
     * will need to modify it so it is thread safe.
     *
     * @see org.directwebremoting.extend.ConverterManager#addConverter(java.lang.Class, org.directwebremoting.extend.Converter)
     */
    public void addConverter(Class clazz, Converter converter)
    {
        // Check that we don't have this one already
        Converter other = convertersByClass.get(clazz);
        if (other != null)
        {
            Loggers.STARTUP.warn("Clash of converters for " + clazz.getName() + ". Using " + converter.getClass().getName() + " in place of " + other.getClass().getName());
        }

        Loggers.STARTUP.debug("- adding converter: " + converter.getClass().getSimpleName() + " for " + clazz.getName());

        converter.setConverterManager(this);

        if (converter instanceof NamedConverter)
        {
            NamedConverter namedConverter = (NamedConverter) converter;
            setUpClassMapping(namedConverter, clazz);
        }

        convertersByClass.put(clazz, converter);
        converterCache.clear();
    }

    /**
     * Does all the initializations for converters using class-mapping (set up
     * superclass, expand wildcards, etc)
     * @param namedConverter converter that may use class-mapping
     * @param clazz the java class to convert using class-mapping
     */
    protected void setUpClassMapping(NamedConverter namedConverter, Class clazz)
    {
        // Set up stuff for mapped JavaScript class (if any)
        if (LocalUtil.hasLength(namedConverter.getJavascript()))
        {
            namedConverter.setJavascript(LocalUtil.inferWildcardReplacements(clazz.getName(), namedConverter.getJavascript()));
            namedConverter.setInstanceType(clazz);

            // Set up stuff for mapped JavaScript superclass (if not already assigned)
            if (!LocalUtil.hasLength(namedConverter.getJavascriptSuperClass()))
            {
                // (Here we depend on that the match string is a non-wildcarded
                // Java class name and the relevant match strings in the
                // converters collection are also non-wildcarded Java
                // class/interface names)

                // First check if any of our super-classes are mapped
                Class javaSuperClass = clazz.getSuperclass();
                while(javaSuperClass != null)
                {
                    String jsSuperClassName = getMappedJavaScriptClassName(javaSuperClass);
                    if (jsSuperClassName != null)
                    {
                        namedConverter.setJavascriptSuperClass(jsSuperClassName);
                        break;
                    }

                    // Continue with next class
                    javaSuperClass = javaSuperClass.getSuperclass();
                }

                // If we still have no superclass then continue trying with interfaces
                if (!LocalUtil.hasLength(namedConverter.getJavascriptSuperClass()))
                {
                    Class checkInterfacesOnClass = clazz;
                    while(checkInterfacesOnClass != null)
                    {
                        String jsSuperClassName = findMappedJavaScriptClassNameForAnyInterface(checkInterfacesOnClass);
                        if (jsSuperClassName != null)
                        {
                            namedConverter.setJavascriptSuperClass(jsSuperClassName);
                            break;
                        }

                        // Continue with next class
                        checkInterfacesOnClass = checkInterfacesOnClass.getSuperclass();
                    }
                }
            }

            convertersByJavascript.put(namedConverter.getJavascript(), namedConverter);
            converterCache.clear();
        }
    }

    /**
     * Convenience method to find a class's mapped JavaScript class name IF
     * the Java class is being converted and IF there is a mapped class name.
     * @param clazz the java class
     * @return a non-empty classname, or null if not found
     */
    protected String getMappedJavaScriptClassName(Class clazz)
    {
        Converter conv = getConverter(clazz);
        if (conv != null)
        {
            // Check if mapped
            if (conv instanceof NamedConverter)
            {
                NamedConverter namedConv = (NamedConverter) conv;
                if (LocalUtil.hasLength(namedConv.getJavascript()))
                {
                    return namedConv.getJavascript();
                }
            }
        }
        return null;
    }

    /**
     * Recurse a class's interface graph and return the first found mapped
     * JavaScript class name.
     * @param checkInterfacesOnClass the java class
     * @return a non-empty classname, or null if not found
     */
    protected String findMappedJavaScriptClassNameForAnyInterface(Class checkInterfacesOnClass)
    {
        Class[] interfaces = checkInterfacesOnClass.getInterfaces();
        for (Class intfc : interfaces)
        {
            // Check if this interface is mapped
            String jsClassName = getMappedJavaScriptClassName(intfc);
            if (jsClassName != null)
            {
                return jsClassName;
            }

            // Otherwise recursively try with the interfaces we are extending
            jsClassName = findMappedJavaScriptClassNameForAnyInterface(intfc);
            if (jsClassName != null)
            {
                return jsClassName;
            }
        }

        return null;
    }

    /* (non-Javadoc)
     * @see org.directwebremoting.ConverterManager#getConverterMatchStrings()
     */
    public Collection getConverterMatchStrings()
    {
        return Collections.unmodifiableSet(convertersByMatch.keySet());
    }

    /* (non-Javadoc)
     * @see org.directwebremoting.ConverterManager#getConverterByMatchString(java.lang.String)
     */
    public Converter getConverterByMatchString(String match)
    {
        return convertersByMatch.get(match);
    }

    /* (non-Javadoc)
     * @see org.directwebremoting.extend.ConverterManager#getNamedConverterJavaScriptNames()
     */
    public Collection getNamedConverterJavaScriptNames()
    {
        return convertersByJavascript.keySet();
    }

    /* (non-Javadoc)
     * @see org.directwebremoting.ConverterManager#getNamedConverter(java.lang.String)
     */
    public NamedConverter getNamedConverter(String javascriptClassName)
    {
        return convertersByJavascript.get(javascriptClassName);
    }

    /* (non-Javadoc)
     * @see org.directwebremoting.ConverterManager#isConvertable(java.lang.Class)
     */
    public boolean isConvertable(Class clazz)
    {
        return getConverter(clazz) != null;
    }

    /* (non-Javadoc)
     * @see org.directwebremoting.extend.ConverterManager#getClientDeclaredType(org.directwebremoting.extend.InboundVariable)
     */
    public Class getClientDeclaredType(InboundVariable data)
    {
        if (data == null)
        {
            return null;
        }

        String objectName = data.getNamedObjectType();
        if (objectName != null)
        {
            NamedConverter converter = getNamedConverter(objectName, Object.class);
            if (converter != null)
            {
                return converter.getInstanceType();
            }
            else
            {
                return null;
            }
        }

        // TODO: we do know the type that the client sent - we might be able
        // to do something better here.
        return null;
    }

    /* (non-Javadoc)
     * @see org.directwebremoting.ConverterManager#convertInbound(java.lang.Class, org.directwebremoting.InboundVariable, org.directwebremoting.InboundContext, org.directwebremoting.TypeHintContext)
     */
    @SuppressWarnings("unchecked")
    public  T convertInbound(Class paramType, InboundVariable data, Property thc) throws ConversionException
    {
        InboundContext context = data.getContext();

        Object converted = context.getConverted(data, paramType);
        if (converted == null)
        {
            Converter converter = null;

            // Was the inbound variable marshalled as an Object in the client
            // (could mean that this is an instance of one of our generated
            // JavaScript classes)
            String type = data.getNamedObjectType();
            if (type != null)
            {
                converter = getNamedConverter(type, paramType);
            }

            // Fall back to the standard way of locating a converter if we
            // didn't find anything above
            if (converter == null)
            {
                converter = getConverter(paramType);
            }

            if (converter == null)
            {
                log.error("Missing converter. Context of conversion: " + thc);
                throw new ConversionException(paramType, "No inbound converter found for property '" + thc.getName() + "' of type '" + paramType.getName() + "'");
            }

            context.pushContext(thc);
            converted = converter.convertInbound(paramType, data);
            context.popContext();
        }

        return (T) converted;
    }

    /* (non-Javadoc)
     * @see org.directwebremoting.extend.ConverterManager#convertInbound(java.lang.Class, org.directwebremoting.io.RawData)
     */
    public  T convertInbound(Class paramType, RawData rawData) throws ConversionException
    {
        RealRawData realRawData = (RealRawData) rawData;
        InboundVariable inboundVariable = realRawData.getInboundVariable();
        return convertInbound(paramType, inboundVariable, null);
    }

    /* (non-Javadoc)
     * @see org.directwebremoting.ConverterManager#convertOutbound(java.lang.Object, org.directwebremoting.OutboundContext)
     */
    public OutboundVariable convertOutbound(Object data, OutboundContext converted) throws ConversionException
    {
        if (data == null)
        {
            return new NonNestedOutboundVariable("null");
        }

        // Check to see if we have done this one already
        if (!converted.isJsonMode())
        {
            OutboundVariable ov = converted.get(data);
            if (ov != null)
            {
                // So the object as been converted already, we just need to refer to it.
                return ov.getReferenceVariable();
            }
        }

        // So we will have to do the conversion
        Converter converter = getConverter(data);
        if (converter == null)
        {
            String message = "No outbound converter found for '" + data.getClass().getName() + "'";
            log.error(message);
            return new ErrorOutboundVariable(message);
        }

        return converter.convertOutbound(data, converted);
    }

    /* (non-Javadoc)
     * @see org.directwebremoting.extend.ConverterManager#setOverrideProperty(org.directwebremoting.extend.Property, org.directwebremoting.extend.Property)
     */
    public void setOverrideProperty(Property original, Property replacement)
    {
        propertyOverrideMap.put(original, replacement);
    }

    /* (non-Javadoc)
     * @see org.directwebremoting.extend.ConverterManager#checkOverride(org.directwebremoting.extend.Property)
     */
    public Property checkOverride(Property property)
    {
        Property override = propertyOverrideMap.get(property);
        if (override != null)
        {
            return override;
        }
        else
        {
            return property;
        }
    }

    /* (non-Javadoc)
     *
     * Called only at start-up! It is important that this method only be called at start-up.  Otherwise we
     * will need to modify it so it is thread safe.
     *
     * @see org.directwebremoting.ConverterManager#setConverters(java.util.Map)
     */
    public void setConverters(Map converters)
    {
        synchronized (this.convertersByMatch)
        {
            this.convertersByMatch.clear();
            this.convertersByMatch.putAll(converters);
            converterCache.clear();
        }
    }

    /**
     * Like getConverter(object.getClass()); except that since the
     * object can be null we check for that fist and then do a lookup against
     * the Void.TYPE converter
     * @param object The object to find a converter for
     * @return The converter for the given type
     */
    private Converter getConverter(Object object)
    {
        if (object == null)
        {
            return getConverter(Void.TYPE);
        }

        return getConverter(object.getClass());
    }

    /**
     * When we are using typed Javascript names we sometimes want to get a
     * specially named converter
     * @param paramType The class that we are converting to
     * @param javascriptClassName The type name as passed in from the client
     * @return The Converter that matches this request (if any)
     * @throws ConversionException If marshalling fails
     */
    protected NamedConverter getNamedConverter(String javascriptClassName, Class paramType) throws ConversionException
    {
        NamedConverter namedConv = getNamedConverter(javascriptClassName);
        if (namedConv != null)
        {
            if (paramType.isAssignableFrom(namedConv.getInstanceType()))
            {
                return namedConv;
            }
        }
        return null;
    }

    /**
     * @param clazz The type to find a converter for
     * @return The converter for the given type, or null if one can't be found
     */
    private Converter getConverter(Class clazz)
    {
        // Can we find a converter assignable to clazz in the HashMap?
        Converter converter = getConverterAssignableFrom(clazz);
        if (converter != null)
        {
            return converter;
        }

        String lookup = LocalUtil.originalDwrClassName(clazz.getName());

        // Before we start trying for a match on package parts we check for
        // dynamic proxies
        if (lookup.startsWith("$Proxy"))
        {
            converter = convertersByMatch.get("$Proxy*");
            if (converter != null)
            {
                converterCache.putIfAbsent(clazz, converter);
                return converter;
            }
        }

        while (true)
        {
            // Can we find a converter using wildcards?
            converter = convertersByMatch.get(lookup + ".*");
            if (converter != null)
            {
                converterCache.putIfAbsent(clazz, converter);
                return converter;
            }

            // Arrays can have wildcards like [L* so we don't require a '.'
            converter = convertersByMatch.get(lookup + '*');
            if (converter != null)
            {
                converterCache.putIfAbsent(clazz, converter);
                return converter;
            }

            // Give up if the name is now empty
            if (lookup.length() == 0)
            {
                break;
            }

            // Strip of the component after the last .
            int lastdot = lookup.lastIndexOf('.');
            if (lastdot != -1)
            {
                lookup = lookup.substring(0, lastdot);
            }
            else
            {
                int arrayMarkers = 0;
                while (lookup.charAt(arrayMarkers) == '[')
                {
                    arrayMarkers++;
                }

                if (arrayMarkers == 0)
                {
                    // so we are out of dots and out of array markers
                    // bail out.
                    break;
                }

                // We want to keep the type marker too
                lookup = lookup.substring(arrayMarkers - 1, arrayMarkers + 1);

                // Now can we find it?
                converter = convertersByMatch.get(lookup);
                if (converter != null)
                {
                    converterCache.putIfAbsent(clazz, converter);
                    return converter;
                }
            }
        }

        return null;
    }

    /**
     * @param clazz The type to find a converter for
     * @return The converter assignable for the given type, or null if one can't be found
     */
    private Converter getConverterAssignableFrom(Class clazz)
    {
        if (clazz == null)
        {
            return null;
        }

        // Can we find a suitable converter already in the converter cache?
        Converter converter = converterCache.get(clazz);
        if (converter != null)
        {
            return converter;
        }

        try {
            // Can we find a matching converter supplied by class?
            converter = convertersByClass.get(clazz);
            if (converter != null)
            {
                return converter;
            }

            // Can we find a matching converter supplied by class name?
            converter = convertersByMatch.get(LocalUtil.originalDwrClassName(clazz.getName()));
            if (converter != null)
            {
                return converter;
            }

            // Lookup all of the interfaces of this class for a match
            for (Class anInterface : clazz.getInterfaces())
            {
                converter = getConverterAssignableFrom(anInterface);
                if (converter != null)
                {
                    return converter;
                }
            }

            // Let's search it in superClass
            converter = getConverterAssignableFrom(clazz.getSuperclass());
            if (converter != null)
            {
                return converter;
            }

            return null;
        }
        finally {
            if (converter != null)
            {
                converterCache.putIfAbsent(clazz, converter);
            }
        }
    }

    /**
     * Where we store real type information behind generic types
     */
    protected Map propertyOverrideMap = new HashMap();

    /**
     * The list of the registered converter types
     */
    protected Map> converterTypes = new HashMap>();

    /**
     * The list of converters registered by class name match string
     */
    protected Map convertersByMatch = new HashMap();

    /**
     * The list of converters registered by class
     */
    protected Map, Converter> convertersByClass = new HashMap, Converter>();

    /**
     * The converter cache used during conversion (gets populated based on registered converters)
     */
    protected final Map convertersByJavascript = new HashMap();

    /**
     * The converter cache used during conversion (gets populated based on registered converters)
     */
    protected final ConcurrentMap, Converter> converterCache = new ConcurrentHashMap, Converter>();

    /**
     * The properties that we don't warn about if they don't exist.
     * @see DefaultConverterManager#addConverter(String, String, Map)
     */
    private static final List ignore = Arrays.asList("converter", "match");

    /**
     * The log stream
     */
    private static final Log log = LogFactory.getLog(DefaultConverterManager.class);
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy