com.evolveum.midpoint.prism.parser.PrismBeanInspector.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.prism.parser.PrismBeanInspector.java

Source

/*
 * Copyright (c) 2010-2015 Evolveum
 *
 * 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 com.evolveum.midpoint.prism.parser;

import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.schema.PrismSchema;
import com.evolveum.midpoint.prism.schema.SchemaDescription;
import com.evolveum.midpoint.prism.xml.XsdTypeMapper;
import com.evolveum.midpoint.util.Handler;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.w3c.dom.Node;

import javax.xml.XMLConstants;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.bind.annotation.XmlSchemaType;
import javax.xml.bind.annotation.XmlType;
import javax.xml.namespace.QName;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author mederly
 */
public class PrismBeanInspector {

    private PrismContext prismContext;

    public PrismBeanInspector(PrismContext prismContext) {
        Validate.notNull(prismContext, "prismContext");
        this.prismContext = prismContext;
    }

    //region Caching mechanism (multiple dimensions)

    interface Getter1<V, P1> {
        V get(P1 param1);
    }

    private <V, P1> V find1(Map<P1, V> cache, P1 param1, Getter1<V, P1> getter) {
        if (cache.containsKey(param1)) {
            return cache.get(param1);
        } else {
            V value = getter.get(param1);
            cache.put(param1, value);
            return value;
        }
    }

    interface Getter2<V, P1, P2> {
        V get(P1 param1, P2 param2);
    }

    private <V, P1, P2> V find2(final Map<P1, Map<P2, V>> cache, final P1 param1, final P2 param2,
            final Getter2<V, P1, P2> getter) {
        Map<P2, V> cache2 = cache.get(param1);
        if (cache2 == null) {
            cache2 = Collections.synchronizedMap(new HashMap());
            cache.put(param1, cache2);
        }
        return find1(cache2, param2, new Getter1<V, P2>() {
            @Override
            public V get(P2 p) {
                return getter.get(param1, p);
            }
        });
    }

    interface Getter3<V, P1, P2, P3> {
        V get(P1 param1, P2 param2, P3 param3);
    }

    private <V, P1, P2, P3> V find3(final Map<P1, Map<P2, Map<P3, V>>> cache, final P1 param1, final P2 param2,
            final P3 param3, final Getter3<V, P1, P2, P3> getter) {
        Map<P2, Map<P3, V>> cache2 = cache.get(param1);
        if (cache2 == null) {
            cache2 = Collections.synchronizedMap(new HashMap());
            cache.put(param1, cache2);
        }
        return find2(cache2, param2, param3, new Getter2<V, P2, P3>() {
            @Override
            public V get(P2 p, P3 q) {
                return getter.get(param1, p, q);
            }
        });
    }
    //endregion

    //region Individual inspection methods - cached versions

    private Map<Class<? extends Object>, String> _determineNamespace = Collections.synchronizedMap(new HashMap());

    String determineNamespace(Class<? extends Object> paramType) {
        return find1(_determineNamespace, paramType, new Getter1<String, Class<? extends Object>>() {
            @Override
            public String get(Class<? extends Object> paramType) {
                return determineNamespaceUncached(paramType);
            }
        });
    }

    private Map<Class<? extends Object>, QName> _determineTypeForClass = Collections.synchronizedMap(new HashMap());

    QName determineTypeForClass(Class<? extends Object> paramType) {
        return find1(_determineTypeForClass, paramType, new Getter1<QName, Class<? extends Object>>() {
            @Override
            public QName get(Class<? extends Object> paramType) {
                return determineTypeForClassUncached(paramType);
            }
        });
    }

    private Map<Field, Map<Method, Boolean>> _isAttribute = Collections.synchronizedMap(new HashMap());

    boolean isAttribute(Field field, Method getter) {
        return find2(_isAttribute, field, getter, new Getter2<Boolean, Field, Method>() {
            @Override
            public Boolean get(Field f, Method m) {
                return isAttributeUncached(f, m);
            }
        });
    }

    private Map<Class, Map<String, Method>> _findSetter = Collections.synchronizedMap(new HashMap());

    <T> Method findSetter(Class<T> beanClass, String fieldName) {
        return find2(_findSetter, beanClass, fieldName, new Getter2<Method, Class, String>() {
            @Override
            public Method get(Class c, String f) {
                return findSetterUncached(c, f);
            }
        });
    }

    private Map<Package, Class> _getObjectFactoryClassPackage = Collections.synchronizedMap(new HashMap());

    Class getObjectFactoryClass(Package aPackage) {
        return find1(_getObjectFactoryClassPackage, aPackage, new Getter1<Class, Package>() {
            @Override
            public Class get(Package p) {
                return getObjectFactoryClassUncached(p);
            }
        });
    }

    private Map<String, Class> _getObjectFactoryClassNamespace = Collections.synchronizedMap(new HashMap());

    Class getObjectFactoryClass(String namespaceUri) {
        return find1(_getObjectFactoryClassNamespace, namespaceUri, new Getter1<Class, String>() {
            @Override
            public Class get(String s) {
                return getObjectFactoryClassUncached(s);
            }
        });
    }

    private Map<Class<? extends Object>, List<String>> _getPropOrder = Collections.synchronizedMap(new HashMap());

    List<String> getPropOrder(Class<? extends Object> beanClass) {
        return find1(_getPropOrder, beanClass, new Getter1<List<String>, Class<? extends Object>>() {
            @Override
            public List<String> get(Class<? extends Object> c) {
                return getPropOrderUncached(c);
            }
        });
    }

    private Map<Class, Map<String, Method>> _findElementMethodInObjectFactory = Collections
            .synchronizedMap(new HashMap());

    Method findElementMethodInObjectFactory(Class objectFactoryClass, String propName) {
        return find2(_findElementMethodInObjectFactory, objectFactoryClass, propName,
                new Getter2<Method, Class, String>() {
                    @Override
                    public Method get(Class c, String p) {
                        return findElementMethodInObjectFactoryUncached(c, p);
                    }
                });
    }

    private Map<Class, Map<Method, Field>> _lookupSubstitution = Collections.synchronizedMap(new HashMap());

    <T> Field lookupSubstitution(Class<T> beanClass, Method elementMethod) {
        return find2(_lookupSubstitution, beanClass, elementMethod, new Getter2<Field, Class, Method>() {
            @Override
            public Field get(Class c, Method m) {
                return lookupSubstitutionUncached(c, m);
            }
        });
    }

    private Map<Class, Map<String, String>> _findEnumFieldName = Collections.synchronizedMap(new HashMap());

    <T> String findEnumFieldName(Class<T> classType, String primValue) {
        return find2(_findEnumFieldName, classType, primValue, new Getter2<String, Class, String>() {
            @Override
            public String get(Class c, String v) {
                return findEnumFieldNameUncached(c, v);
            }
        });
    }

    private Map<Class, Map<String, String>> _findEnumFieldValue = Collections.synchronizedMap(new HashMap());

    <T> String findEnumFieldValue(Class<T> classType, String toStringValue) {
        return find2(_findEnumFieldValue, classType, toStringValue, new Getter2<String, Class, String>() {
            @Override
            public String get(Class c, String v) {
                return findEnumFieldValueUncached(c, v);
            }
        });
    }

    private Map<Field, Map<Class<? extends Object>, Map<String, QName>>> _findFieldTypeName = Collections
            .synchronizedMap(new HashMap());

    QName findFieldTypeName(Field field, Class<? extends Object> beanClass, String defaultNamespacePlaceholder) {
        return find3(_findFieldTypeName, field, beanClass, defaultNamespacePlaceholder,
                new Getter3<QName, Field, Class<? extends Object>, String>() {
                    @Override
                    public QName get(Field field, Class<? extends Object> beanClass,
                            String defaultNamespacePlaceholder) {
                        return findFieldTypeNameUncached(field, beanClass, defaultNamespacePlaceholder);
                    }
                });
    }

    private Map<String, Map<Class<? extends Object>, Map<String, QName>>> _findFieldElementQName = Collections
            .synchronizedMap(new HashMap());

    QName findFieldElementQName(String fieldName, Class<? extends Object> beanClass, String defaultNamespace) {
        return find3(_findFieldElementQName, fieldName, beanClass, defaultNamespace,
                new Getter3<QName, String, Class<? extends Object>, String>() {
                    @Override
                    public QName get(String fieldName, Class<? extends Object> beanClass, String defaultNamespace) {
                        return findFieldElementQNameUncached(fieldName, beanClass, defaultNamespace);
                    }
                });
    }

    private Map<Class, Map<String, Method>> _findPropertyGetter = Collections.synchronizedMap(new HashMap());

    public <T> Method findPropertyGetter(Class<T> beanClass, String propName) {
        return find2(_findPropertyGetter, beanClass, propName, new Getter2<Method, Class, String>() {
            @Override
            public Method get(Class param1, String param2) {
                return findPropertyGetterUncached(param1, param2);
            }
        });
    }

    private Map<Class, Map<String, Field>> _findPropertyField = Collections.synchronizedMap(new HashMap());

    public <T> Field findPropertyField(Class<T> beanClass, String propName) {
        return find2(_findPropertyField, beanClass, propName, new Getter2<Field, Class, String>() {
            @Override
            public Field get(Class param1, String param2) {
                return findPropertyFieldUncached(param1, param2);
            }
        });
    }
    //endregion

    //region Uncached versions of the inspection methods

    private <T> Field findPropertyFieldUncached(Class<T> classType, String propName) {
        Field field = findPropertyFieldExactUncached(classType, propName);
        if (field != null) {
            return field;
        }
        // Fields for some reserved words are prefixed by underscore, so try also this.
        return findPropertyFieldExactUncached(classType, "_" + propName);
    }

    private <T> Field findPropertyFieldExactUncached(Class<T> classType, String propName) {
        for (Field field : classType.getDeclaredFields()) {
            XmlElement xmlElement = field.getAnnotation(XmlElement.class);
            if (xmlElement != null && xmlElement.name() != null && xmlElement.name().equals(propName)) {
                return field;
            }
            XmlAttribute xmlAttribute = field.getAnnotation(XmlAttribute.class);
            if (xmlAttribute != null && xmlAttribute.name() != null && xmlAttribute.name().equals(propName)) {
                return field;
            }
        }
        try {
            return classType.getDeclaredField(propName);
        } catch (NoSuchFieldException e) {
            // nothing found
        }
        Class<? super T> superclass = classType.getSuperclass();
        if (superclass.equals(Object.class)) {
            return null;
        }
        return findPropertyField(superclass, propName);
    }

    private <T> Method findPropertyGetterUncached(Class<T> classType, String propName) {
        if (propName.startsWith("_")) {
            propName = propName.substring(1);
        }
        for (Method method : classType.getDeclaredMethods()) {
            XmlElement xmlElement = method.getAnnotation(XmlElement.class);
            if (xmlElement != null && xmlElement.name() != null && xmlElement.name().equals(propName)) {
                return method;
            }
            XmlAttribute xmlAttribute = method.getAnnotation(XmlAttribute.class);
            if (xmlAttribute != null && xmlAttribute.name() != null && xmlAttribute.name().equals(propName)) {
                return method;
            }
        }
        String getterName = "get" + StringUtils.capitalize(propName);
        try {
            return classType.getDeclaredMethod(getterName);
        } catch (NoSuchMethodException e) {
            // nothing found
        }
        getterName = "is" + StringUtils.capitalize(propName);
        try {
            return classType.getDeclaredMethod(getterName);
        } catch (NoSuchMethodException e) {
            // nothing found
        }
        Class<? super T> superclass = classType.getSuperclass();
        if (superclass.equals(Object.class)) {
            return null;
        }
        return findPropertyGetter(superclass, propName);
    }

    private boolean isAttributeUncached(Field field, Method getter) {
        if (field == null && getter == null) {
            return false;
        }

        if (field != null && field.isAnnotationPresent(XmlAttribute.class)) {
            return true;
        }

        if (getter != null && getter.isAnnotationPresent(XmlAttribute.class)) {
            return true;
        }

        return false;
    }

    private String determineNamespaceUncached(Class<? extends Object> beanClass) {
        XmlType xmlType = beanClass.getAnnotation(XmlType.class);
        if (xmlType == null) {
            return null;
        }

        String namespace = xmlType.namespace();
        if (namespace == null || PrismBeanConverter.DEFAULT_PLACEHOLDER.equals(namespace)) {
            XmlSchema xmlSchema = beanClass.getPackage().getAnnotation(XmlSchema.class);
            namespace = xmlSchema.namespace();
        }
        if (StringUtils.isBlank(namespace) || PrismBeanConverter.DEFAULT_PLACEHOLDER.equals(namespace)) {
            return null;
        }

        return namespace;
    }

    private QName determineTypeForClassUncached(Class<? extends Object> beanClass) {
        XmlType xmlType = beanClass.getAnnotation(XmlType.class);
        if (xmlType == null) {
            return null;
        }

        String namespace = xmlType.namespace();
        if (namespace == null || PrismBeanConverter.DEFAULT_PLACEHOLDER.equals(namespace)) {
            XmlSchema xmlSchema = beanClass.getPackage().getAnnotation(XmlSchema.class);
            namespace = xmlSchema.namespace();
        }
        if (StringUtils.isBlank(namespace) || PrismBeanConverter.DEFAULT_PLACEHOLDER.equals(namespace)) {
            return null;
        }

        return new QName(namespace, xmlType.name());
    }

    private <T> Method findSetterUncached(Class<T> classType, String fieldName) {
        String setterName = getSetterName(fieldName);
        for (Method method : classType.getMethods()) {
            if (!method.getName().equals(setterName)) {
                continue;
            }
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length != 1) {
                continue;
            }
            Class<?> setterType = parameterTypes[0];
            if (setterType.equals(Object.class) || Node.class.isAssignableFrom(setterType)) {
                // Leave for second pass, let's try find a better setter
                continue;
            }
            return method;
        }
        // Second pass
        for (Method method : classType.getMethods()) {
            if (!method.getName().equals(setterName)) {
                continue;
            }
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length != 1) {
                continue;
            }
            return method;
        }
        return null;
    }

    private String getSetterName(String fieldName) {
        if (fieldName.startsWith("_")) {
            fieldName = fieldName.substring(1);
        }
        return "set" + StringUtils.capitalize(fieldName);
    }

    private Class getObjectFactoryClassUncached(Package pkg) {
        try {
            return Class.forName(pkg.getName() + ".ObjectFactory");
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException(
                    "Cannot find object factory class in package " + pkg.getName() + ": " + e.getMessage(), e);
        }
    }

    private Class getObjectFactoryClassUncached(String namespaceUri) {
        SchemaDescription schemaDescription = prismContext.getSchemaRegistry()
                .findSchemaDescriptionByNamespace(namespaceUri);
        if (schemaDescription == null) {
            throw new IllegalArgumentException("Cannot find object factory class for namespace " + namespaceUri
                    + ": unknown schema namespace");
        }
        Package compileTimeClassesPackage = schemaDescription.getCompileTimeClassesPackage();
        if (compileTimeClassesPackage == null) {
            throw new IllegalArgumentException("Cannot find object factory class for namespace " + namespaceUri
                    + ": not a compile-time schema");
        }
        return getObjectFactoryClassUncached(compileTimeClassesPackage);
    }

    private Method findElementMethodInObjectFactoryUncached(Class objectFactoryClass, String propName) {
        for (Method method : objectFactoryClass.getDeclaredMethods()) {
            XmlElementDecl xmlElementDecl = method.getAnnotation(XmlElementDecl.class);
            if (xmlElementDecl == null) {
                continue;
            }
            if (propName.equals(xmlElementDecl.name())) {
                return method;
            }
        }
        return null;
    }

    private Field lookupSubstitutionUncached(Class beanClass, Method elementMethod) {
        XmlElementDecl xmlElementDecl = elementMethod.getAnnotation(XmlElementDecl.class);
        if (xmlElementDecl == null) {
            return null;
        }
        final String substitutionHeadName = xmlElementDecl.substitutionHeadName();
        if (substitutionHeadName == null) {
            return null;
        }
        return findField(beanClass, new Handler<Field>() {
            @Override
            public boolean handle(Field field) {
                XmlElementRef xmlElementRef = field.getAnnotation(XmlElementRef.class);
                if (xmlElementRef == null) {
                    return false;
                }
                String name = xmlElementRef.name();
                if (name == null) {
                    return false;
                }
                return name.equals(substitutionHeadName);
            }
        });
    }

    private Field findField(Class classType, Handler<Field> selector) {
        for (Field field : classType.getDeclaredFields()) {
            if (selector.handle(field)) {
                return field;
            }
        }
        Class superclass = classType.getSuperclass();
        if (superclass.equals(Object.class)) {
            return null;
        }
        return findField(superclass, selector);
    }

    private Method findMethod(Class classType, Handler<Method> selector) {
        for (Method field : classType.getDeclaredMethods()) {
            if (selector.handle(field)) {
                return field;
            }
        }
        Class superclass = classType.getSuperclass();
        if (superclass.equals(Object.class)) {
            return null;
        }
        return findMethod(superclass, selector);
    }

    private List<String> getPropOrderUncached(Class<? extends Object> beanClass) {
        List<String> propOrder;

        // Superclass first!
        Class superclass = beanClass.getSuperclass();
        if (superclass.equals(Object.class) || superclass.getAnnotation(XmlType.class) == null) {
            propOrder = new ArrayList<>();
        } else {
            propOrder = new ArrayList<>(getPropOrder(superclass));
        }

        XmlType xmlType = beanClass.getAnnotation(XmlType.class);
        if (xmlType == null) {
            throw new IllegalArgumentException(
                    "Cannot marshall " + beanClass + " it does not have @XmlType annotation");
        }

        String[] myPropOrder = xmlType.propOrder();
        if (myPropOrder != null) {
            for (String myProp : myPropOrder) {
                if (StringUtils.isNotBlank(myProp)) {
                    // some properties starts with underscore..we don't want to serialize them with underscore, so remove it..
                    if (myProp.startsWith("_")) {
                        myProp = myProp.replace("_", "");
                    }
                    propOrder.add(myProp);
                }
            }
        }

        Field[] fields = beanClass.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            if (field.isAnnotationPresent(XmlAttribute.class)) {
                propOrder.add(field.getName());
            }
        }

        Method[] methods = beanClass.getDeclaredMethods();
        for (int i = 0; i < methods.length; i++) {
            Method method = methods[i];
            if (method.isAnnotationPresent(XmlAttribute.class)) {
                //            System.out.println("methodName: " + method.getName());
                String propname = getPropertyNameFromGetter(method.getName());
                //StringUtils.uncapitalize(StringUtils.removeStart("get", method.getName()))
                propOrder.add(propname);
            }
        }

        return propOrder;
    }

    private <T> String findEnumFieldNameUncached(Class classType, T primValue) {
        for (Field field : classType.getDeclaredFields()) {
            XmlEnumValue xmlEnumValue = field.getAnnotation(XmlEnumValue.class);
            if (xmlEnumValue != null && xmlEnumValue.value() != null && xmlEnumValue.value().equals(primValue)) {
                return field.getName();
            }
        }
        return null;
    }

    public static String findEnumFieldValueUncached(Class classType, String toStringValue) {
        for (Field field : classType.getDeclaredFields()) {
            XmlEnumValue xmlEnumValue = field.getAnnotation(XmlEnumValue.class);
            if (xmlEnumValue != null && field.getName().equals(toStringValue)) {
                return xmlEnumValue.value();
            }
        }
        return null;
    }

    private String getPropertyNameFromGetter(String getterName) {
        if ((getterName.length() > 3) && getterName.startsWith("get")
                && Character.isUpperCase(getterName.charAt(3))) {
            String propPart = getterName.substring(3);
            return StringUtils.uncapitalize(propPart);
        }
        return getterName;
    }

    private QName findFieldTypeNameUncached(Field field, Class fieldType, String schemaNamespace) {
        QName propTypeQname = null;
        XmlSchemaType xmlSchemaType = null;
        if (field != null) {
            xmlSchemaType = field.getAnnotation(XmlSchemaType.class);
        }
        if (xmlSchemaType != null) {
            String propTypeLocalPart = xmlSchemaType.name();
            if (propTypeLocalPart != null) {
                String propTypeNamespace = xmlSchemaType.namespace();
                if (propTypeNamespace == null) {
                    propTypeNamespace = XMLConstants.W3C_XML_SCHEMA_NS_URI;
                }
                propTypeQname = new QName(propTypeNamespace, propTypeLocalPart);
            }
        }
        if (propTypeQname == null) {
            propTypeQname = XsdTypeMapper.getJavaToXsdMapping(fieldType);
        }

        if (propTypeQname == null) {
            XmlType xmlType = (XmlType) fieldType.getAnnotation(XmlType.class);
            if (xmlType != null) {
                String propTypeLocalPart = xmlType.name();
                if (propTypeLocalPart != null) {
                    String propTypeNamespace = xmlType.namespace();
                    if (propTypeNamespace == null
                            || propTypeNamespace.equals(PrismBeanConverter.DEFAULT_PLACEHOLDER)) {
                        if (prismContext != null) { // hopefully this is always the case!
                            PrismSchema schema = prismContext.getSchemaRegistry()
                                    .findSchemaByCompileTimeClass(fieldType);
                            if (schema != null && schema.getNamespace() != null) {
                                propTypeNamespace = schema.getNamespace();
                            }
                        }
                        if (propTypeNamespace == null) {
                            // schemaNamespace is only a poor indicator of required namespace (consider e.g. having c:UserType in apit:ObjectListType)
                            // so we use it only if we couldn't find anything else
                            propTypeNamespace = schemaNamespace;
                        }
                    }
                    propTypeQname = new QName(propTypeNamespace, propTypeLocalPart);
                }
            }
        }

        return propTypeQname;
    }

    private QName findFieldElementQNameUncached(String fieldName, Class beanClass, String defaultNamespace) {
        Field field;
        try {
            field = beanClass.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            return new QName(defaultNamespace, fieldName); // TODO implement this if needed (lookup the getter method instead of the field)
        }
        String realLocalName = fieldName;
        String realNamespace = defaultNamespace;
        XmlElement xmlElement = field.getAnnotation(XmlElement.class);
        if (xmlElement != null) {
            String name = xmlElement.name();
            if (name != null && !PrismBeanConverter.DEFAULT_PLACEHOLDER.equals(name)) {
                realLocalName = name;
            }
            String namespace = xmlElement.namespace();
            if (namespace != null && !PrismBeanConverter.DEFAULT_PLACEHOLDER.equals(namespace)) {
                realNamespace = namespace;
            }
        }
        return new QName(realNamespace, realLocalName);
    }
    //endregion

    public <T> Field findAnyField(Class<T> beanClass) {
        return findField(beanClass, new Handler<Field>() {
            @Override
            public boolean handle(Field field) {
                return (field.getAnnotation(XmlAnyElement.class) != null);
            }
        });
    }

    public <T> Method findAnyMethod(Class<T> beanClass) {
        return findMethod(beanClass, new Handler<Method>() {
            @Override
            public boolean handle(Method method) {
                return (method.getAnnotation(XmlAnyElement.class) != null);
            }
        });
    }
}