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

org.apache.tuscany.sca.databinding.jaxb.JAXBContextCache Maven / Gradle / Ivy

There is a newer version: 2.0.1
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.tuscany.sca.databinding.jaxb;

import java.awt.Image;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ref.SoftReference;
import java.net.URI;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import javax.activation.DataHandler;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.transform.Source;

import org.apache.tuscany.sca.common.java.collection.LRUCache;
import org.apache.tuscany.sca.core.ExtensionPointRegistry;
import org.apache.tuscany.sca.extensibility.ClassLoaderContext;

/**
 * @version $Rev: 909173 $ $Date: 2010-02-12 01:23:50 +0200 (Fri, 12 Feb 2010) $
 */
public class JAXBContextCache {
    private static final int CACHE_SIZE = 128;

    private static HashMap> loadClassMap = new HashMap>();

    static {
        loadClassMap.put("byte", byte.class);
        loadClassMap.put("int", int.class);
        loadClassMap.put("short", short.class);
        loadClassMap.put("long", long.class);
        loadClassMap.put("float", float.class);
        loadClassMap.put("double", double.class);
        loadClassMap.put("boolean", boolean.class);
        loadClassMap.put("char", char.class);
        loadClassMap.put("void", void.class);
    }

    protected static Class[] JAXB_BUILTIN_CLASSES =
        {byte[].class, boolean.class, byte.class, char.class, double.class, float.class, int.class, long.class,
         short.class, void.class, java.awt.Image.class, java.io.File.class, java.lang.Boolean.class,
         java.lang.Byte.class, java.lang.Character.class, java.lang.Class.class, java.lang.Double.class,
         java.lang.Float.class, java.lang.Integer.class, java.lang.Long.class, java.lang.Object.class,
         java.lang.Short.class, java.lang.String.class, java.lang.Void.class, java.math.BigDecimal.class,
         java.math.BigInteger.class, java.net.URI.class, java.net.URL.class, java.util.Calendar.class,
         java.util.Date.class, java.util.GregorianCalendar.class, java.util.UUID.class,
         javax.activation.DataHandler.class, javax.xml.bind.JAXBElement.class, javax.xml.datatype.Duration.class,
         javax.xml.datatype.XMLGregorianCalendar.class, javax.xml.namespace.QName.class,
         javax.xml.transform.Source.class};

    protected static final Set> BUILTIN_CLASSES_SET = new HashSet>(Arrays.asList(JAXB_BUILTIN_CLASSES));

    /*
    protected static Class[] COMMON_ARRAY_CLASSES =
        new Class[] {char[].class, short[].class, int[].class, long[].class, float[].class, double[].class,
                     String[].class
                     };

    protected static final Set> COMMON_CLASSES_SET = new HashSet>(Arrays.asList(COMMON_ARRAY_CLASSES));
    */

    protected LRUCache cache;
    protected Pool  mpool;
    protected Pool upool;

    // protected JAXBContext commonContext;
    protected JAXBContext defaultContext;
    private ExtensionPointRegistry registry;
    
    public JAXBContextCache(ExtensionPointRegistry registry) {
        this(CACHE_SIZE, CACHE_SIZE, CACHE_SIZE, registry);
    }

    public JAXBContextCache(int contextSize, int marshallerSize, int unmarshallerSize, ExtensionPointRegistry registry) {
        this.registry = registry;
        cache = new LRUCache(contextSize);
        mpool = new Pool();
        upool = new Pool();
        defaultContext = getDefaultJAXBContext();
    }
    
    private JAXBContext newJAXBContext(final Class... classesToBeBound) throws JAXBException {
        try {
            return AccessController.doPrivileged(new PrivilegedExceptionAction() {
                public JAXBContext run() throws JAXBException {
                    // Try to set up TCCL so that JAXBContext service discovery works in OSGi
                    ClassLoader tccl =
                        ClassLoaderContext.setContextClassLoader(JAXBContextCache.class.getClassLoader(),
                                                                 registry.getServiceDiscovery(),
                                                                 // The service provider of JAXBContext doesn't extend JAXBContext
                                                                 // We should use the service name instead of the class
                                                                 JAXBContext.class.getName(), 
                                                                 DatatypeFactory.class.getName());
                    try {
                        JAXBContext context = JAXBContext.newInstance(classesToBeBound);
                        return context;
                    } finally {
                        if (tccl != null) {
                            Thread.currentThread().setContextClassLoader(tccl);
                        }
                    }
                }
            });
        } catch (PrivilegedActionException e) {
            throw (JAXBException)e.getException();
        }
    }


    public JAXBContext getDefaultJAXBContext() {
        try {
            return newJAXBContext();
        } catch (JAXBException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * @param name of primitive type
     * @return primitive Class or null
     */
    public static Class getPrimitiveClass(String text) {
        return loadClassMap.get(text);
    }

    /**
     * Return the class for this name
     *
     * @return Class
     */
    private static Class forName(final String className, final boolean initialize, final ClassLoader classloader)
        throws ClassNotFoundException {
        // NOTE: This method must remain private because it uses AccessController
        Class cl = null;
        try {
            cl = AccessController.doPrivileged(new PrivilegedExceptionAction>() {
                public Class run() throws ClassNotFoundException {
                    // Class.forName does not support primitives
                    Class cls = getPrimitiveClass(className);
                    if (cls == null) {
                        cls = Class.forName(className, initialize, classloader);
                    }
                    return cls;
                }
            });
        } catch (PrivilegedActionException e) {
            throw (ClassNotFoundException)e.getException();
        }

        return cl;
    }

    public Marshaller getMarshaller(JAXBContext context) throws JAXBException {
        Marshaller marshaller = mpool.get(context);
        if (marshaller == null) {
            marshaller = context.createMarshaller();
        }
        marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
        return marshaller;
    }

    public void releaseJAXBMarshaller(JAXBContext context, Marshaller marshaller) {
        if (marshaller != null) {
            marshaller.setAttachmentMarshaller(null);
            mpool.put(context, marshaller);
            // No point unsetting marshaller's JAXB_FRAGMENT property, since we'll just reset it when
            // doing the next get.
        }
    }

    public Unmarshaller getUnmarshaller(JAXBContext context) throws JAXBException {
        Unmarshaller unmarshaller = upool.get(context);
        if (unmarshaller == null) {
            unmarshaller = context.createUnmarshaller();
        }
        return unmarshaller;
    }

    public void releaseJAXBUnmarshaller(JAXBContext context, Unmarshaller unmarshaller) {
        if (unmarshaller != null) {
            unmarshaller.setAttachmentUnmarshaller(null);
            upool.put(context, unmarshaller);
        }
    }

    public LRUCache getCache() {
        return cache;
    }

    public JAXBContext getJAXBContext(Class cls) throws JAXBException {
        if (BUILTIN_CLASSES_SET.contains(cls)) {
            return defaultContext;
        }
        return getJAXBContext(new Class[] {cls});
    }

    public JAXBContext getJAXBContext(Class[] classes) throws JAXBException {
        Set> classSet = new HashSet>(Arrays.asList(classes));
        return getJAXBContext(classSet);
    }

    public JAXBContext getJAXBContext(Set> classes) throws JAXBException {
        // Remove the JAXB built-in types to maximize the cache hit
        Set> classSet = new HashSet>(classes);
        classSet.removeAll(BUILTIN_CLASSES_SET);

        // FIXME: [rfeng] Remove java classes that are mapped to the same XSD type to avoid
        // conflicts
        if (classSet.contains(Date[].class)) {
            classSet.remove(Calendar[].class);
        }

        if (classSet.contains(URI[].class)) {
            classSet.remove(UUID[].class);
        }

        if (classSet.contains(Source[].class)) {
            classSet.remove(Image[].class);
            classSet.remove(DataHandler[].class);
        }

        classSet = getJAXBClasses(classSet);

        if(classSet.isEmpty()) {
            return defaultContext;
        }

        synchronized (cache) {
            JAXBContext context = cache.get(classSet);
            if (context != null) {
                return context;
            }
            context = newJAXBContext(classSet.toArray(new Class[classSet.size()]));
            cache.put(classSet, context);
            return context;
        }
    }

    public void clear() {
        synchronized (cache) {
            cache.clear();
        }
        /*
        synchronized (upool) {
            upool.clear();
        }
        synchronized (upool) {
            upool.clear();
        }
        */
    }

    //
    // This inner class is copied in its entirety from the Axis2 utility class,
    // org.apache.axis2.jaxws.message.databinding.JAXBUtils.   We could look into extending but it's such a basic data structure
    // without other dependencies so we might be better off copying it and avoiding a new
    // Axis2 dependency here.
    //

    /**
     * Pool a list of items for a specific key
     *
     * @param  Key
     * @param  Pooled object
     */
    private static class Pool {
        private SoftReference>> softMap =
            new SoftReference>>(
                    new ConcurrentHashMap>());

        // The maps are freed up when a LOAD FACTOR is hit
        private static final int MAX_LIST_FACTOR = 50;
        private static final int MAX_LOAD_FACTOR = 32;  // Maximum number of JAXBContext to store

        /**
         * @param key
         * @return removed item from pool or null.
         */
        public V get(K key) {
            List values = getValues(key);
            synchronized (values) {
                if (values.size()>0) {
                    V v = values.remove(values.size()-1);
                    return v;

                }
            }
            return null;
        }

        /**
         * Add item back to pool
         * @param key
         * @param value
         */
        public void put(K key, V value) {
            adjustSize();
            List values = getValues(key);
            synchronized (values) {
                if (values.size() < MAX_LIST_FACTOR) {
                    values.add(value);
                }
            }
        }

        /**
         * Get or create a list of the values for the key
         * @param key
         * @return list of values.
         */
        private List getValues(K key) {
            Map> map = softMap.get();
            List values = null;
            if (map != null) {
                values = map.get(key);
                if(values !=null) {
                    return values;
                }
            }
            synchronized (this) {
                if (map != null) {
                    values = map.get(key);
                }
                if (values == null) {
                    if (map == null) {
                        map = new ConcurrentHashMap>();
                        softMap =
                            new SoftReference>>(map);
                    }
                    values = new ArrayList();
                    map.put(key, values);

                }
                return values;
            }
        }

        /**
         * AdjustSize
         * When the number of keys exceeds the maximum load, half
         * of the entries are deleted.
         *
         * The assumption is that the JAXBContexts, UnMarshallers, Marshallers, etc. require
         * a large footprint.
         */
        private void adjustSize() {
            Map> map = softMap.get();
            if (map != null && map.size() > MAX_LOAD_FACTOR) {
                // Remove every other Entry in the map.
                Iterator it = map.entrySet().iterator();
                boolean removeIt = false;
                while (it.hasNext()) {
                    it.next();
                    if (removeIt) {
                        it.remove();
                    }
                    removeIt = !removeIt;
                }
            }
        }
    }

    /**
     * Find the JAXB classes (looking into packages) to be bound
     * @param classes A collection of classes
     * @return A set of classes that include the ObjectFactory and indexed JAXB classes
     * @throws JAXBException
     */
    private static Set> getJAXBClasses(Collection> classes) throws JAXBException {
        Set> classSet = new HashSet>();
        // Index the packages
        Map pkgs = getPackages(classes);
        Set nonJAXBPackages = new HashSet();
        for (Map.Entry p : pkgs.entrySet()) {
            Package pkg = p.getKey();
            if (pkg == null) {
                continue;
            }
            Set> set = getJAXBClasses(pkg.getName(), p.getValue());
            if (set.isEmpty()) {
                // No JAXB package
                nonJAXBPackages.add(pkg);
            } else {
                // Add JAXB ObjectFactory and indexed classes
                classSet.addAll(set);
            }
        }
        // Adding classes that are not part of JAXB packages
        for (Class cls : classes) {

            Package pkg = getPackage(cls);
            if (pkg == null || nonJAXBPackages.contains(pkg)) {
                classSet.add(cls);
            } else {
                // TUSCANY-3162: Test if a class is generated by JAXB
                // There might be the case that non-JAXB classes are in the same package as the JAXB classes
                if (!cls.isAnnotationPresent(XmlType.class) 
                    && !cls.isAnnotationPresent(XmlEnum.class)
                    && !cls.isAnnotationPresent(XmlSeeAlso.class)
                    && !cls.isAnnotationPresent(XmlRootElement.class)
                    && !cls.isAnnotationPresent(XmlTransient.class)) {
                    classSet.add(cls);
                }
            }
        }
        return classSet;
    }

    /**
     * Get the package for a class, taking array into account
     * @param cls
     * @return
     */
    private static Package getPackage(Class cls) {
        Class type = cls;
        while (type.isArray()) {
            type = type.getComponentType();
        }
        return type.getPackage();
    }

    /**
     * Get a map of packages
     * @param classes
     * @return
     */
    private static Map getPackages(Collection> classes) {
        Map pkgs = new HashMap();
        for (Class cls : classes) {
            Package pkg = getPackage(cls);
            if (pkg != null) {
                pkgs.put(pkg, cls.getClassLoader());
            }
        }
        return pkgs;
    }

    /**
     * Find ObjectFactory and indexed JAXB classes for the package
     * @param pkg
     * @param classLoader
     * @return
     * @throws JAXBException
     */
    private static Set> getJAXBClasses(String pkg, ClassLoader classLoader) throws JAXBException {
        Set> classes = new HashSet>();
        List> indexedClasses;

        // look for ObjectFactory and load it
        final Class o;
        try {
            o = forName(pkg + ".ObjectFactory", false, classLoader);
            classes.add(o);
        } catch (ClassNotFoundException e) {
            // not necessarily an error
        }

        // look for jaxb.index and load the list of classes
        try {
            indexedClasses = loadIndexedClasses(pkg, classLoader);
        } catch (IOException e) {
            throw new JAXBException(e);
        }
        if (indexedClasses != null) {
            classes.addAll(indexedClasses);
        }

        return classes;
    }

    /**
     * Look for jaxb.index file in the specified package and load it's contents
     *
     * @param pkg package name to search in
     * @param classLoader ClassLoader to search in
     * @return a List of Class objects to load, null if there weren't any
     * @throws IOException if there is an error reading the index file
     * @throws JAXBException if there are any errors in the index file
     */
    private static List> loadIndexedClasses(String pkg, ClassLoader classLoader) throws IOException,
        JAXBException {
        if (classLoader == null) {
            return null;
        }
        final String resource = pkg.replace('.', '/') + "/jaxb.index";
        final InputStream resourceAsStream = classLoader.getResourceAsStream(resource);

        if (resourceAsStream == null) {
            return null;
        }

        BufferedReader in = new BufferedReader(new InputStreamReader(resourceAsStream, "UTF-8"));
        try {
            List> classes = new ArrayList>();
            String className = in.readLine();
            while (className != null) {
                className = className.trim();
                if (className.startsWith("#") || (className.length() == 0)) {
                    className = in.readLine();
                    continue;
                }

                try {
                    classes.add(forName(pkg + '.' + className, false, classLoader));
                } catch (ClassNotFoundException e) {
                    throw new JAXBException(e);
                }

                className = in.readLine();
            }
            return classes;
        } finally {
            in.close();
        }
    }

}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy