com.evolveum.midpoint.repo.sql.query2.definition.ClassDefinitionParser.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.repo.sql.query2.definition.ClassDefinitionParser.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.repo.sql.query2.definition;

import com.evolveum.midpoint.prism.path.IdentifierPathSegment;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.path.ItemPathSegment;
import com.evolveum.midpoint.prism.path.NameItemPathSegment;
import com.evolveum.midpoint.prism.path.ParentPathSegment;
import com.evolveum.midpoint.repo.sql.data.Marker;
import com.evolveum.midpoint.repo.sql.data.common.ObjectReference;
import com.evolveum.midpoint.repo.sql.data.common.RObject;
import com.evolveum.midpoint.repo.sql.data.common.embedded.RPolyString;
import com.evolveum.midpoint.repo.sql.query.definition.Any;
import com.evolveum.midpoint.repo.sql.query.definition.JaxbName;
import com.evolveum.midpoint.repo.sql.query.definition.JaxbPath;
import com.evolveum.midpoint.repo.sql.query.definition.JaxbType;
import com.evolveum.midpoint.repo.sql.query.definition.OwnerGetter;
import com.evolveum.midpoint.repo.sql.query.definition.OwnerIdGetter;
import com.evolveum.midpoint.repo.sql.query.definition.QueryEntity;
import com.evolveum.midpoint.repo.sql.query.definition.VirtualAny;
import com.evolveum.midpoint.repo.sql.query.definition.VirtualCollection;
import com.evolveum.midpoint.repo.sql.query.definition.VirtualEntity;
import com.evolveum.midpoint.repo.sql.util.ClassMapper;
import com.evolveum.midpoint.schema.SchemaConstantsGenerated;
import com.evolveum.midpoint.schema.constants.ObjectTypes;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import org.apache.commons.lang.StringUtils;
import org.hibernate.annotations.Index;

import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Enumerated;
import javax.persistence.Lob;
import javax.persistence.Transient;
import javax.xml.namespace.QName;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * @author lazyman
 * @author mederly
 */
public class ClassDefinitionParser {

    private static final Trace LOGGER = TraceManager.getTrace(ClassDefinitionParser.class);

    public JpaEntityDefinition parseRootClass(Class jpaClass) {
        return parseClass(jpaClass);
    }

    private JpaEntityDefinition parseClass(Class jpaClass) {

        Class jaxbClass = getJaxbClassForEntity(jpaClass);
        JpaEntityDefinition entity = new JpaEntityDefinition(jpaClass, jaxbClass);
        LOGGER.trace("### {}", entity);

        addVirtualDefinitions(jpaClass, entity);
        Method[] methods = jpaClass.getMethods();

        for (Method method : methods) {
            String methodName = method.getName();
            if (Modifier.isStatic(method.getModifiers()) || "getClass".equals(methodName)
                    || (!methodName.startsWith("is") && !methodName.startsWith("get"))
                    || method.getAnnotation(NotQueryable.class) != null) {
                //it's not getter for queryable property
                continue;
            }

            if (method.isAnnotationPresent(Transient.class)) {
                continue;
            }

            LOGGER.trace("# {}", method);

            JpaLinkDefinition linkDefinition;
            OwnerGetter ownerGetter = method.getAnnotation(OwnerGetter.class);
            if (ownerGetter != null) {
                String jpaName = getJpaName(method);
                JpaDataNodeDefinition nodeDefinition = new JpaEntityPointerDefinition(ownerGetter.ownerClass());
                // Owner is considered as not embedded, so we generate left outer join to access it
                // (instead of implicit inner join that would be used if we would do x.owner.y = '...')
                linkDefinition = new JpaLinkDefinition(new ParentPathSegment(), jpaName, null, false,
                        nodeDefinition);
            } else {
                linkDefinition = parseMethod(method);
            }
            entity.addDefinition(linkDefinition);
        }
        return entity;
    }

    private JpaLinkDefinition parseMethod(Method method) {
        CollectionSpecification collectionSpecification; // non-null if return type is Set<X>, null if it's X
        Type returnedContentType; // X in return type, which is either X or Set<X>
        if (Set.class.isAssignableFrom(method.getReturnType())) {
            // e.g. Set<RObject> or Set<String> or Set<REmbeddedReference<RFocus>>
            Type returnType = method.getGenericReturnType();
            if (!(returnType instanceof ParameterizedType)) {
                throw new IllegalStateException("Method " + method + " returns a non-parameterized collection");
            }
            returnedContentType = ((ParameterizedType) returnType).getActualTypeArguments()[0];
            collectionSpecification = new CollectionSpecification();
        } else {
            returnedContentType = method.getReturnType();
            collectionSpecification = null;
        }

        ItemPath itemPath = getJaxbName(method);
        String jpaName = getJpaName(method);
        Class jpaClass = getClass(returnedContentType);

        // sanity check
        if (Set.class.isAssignableFrom(jpaClass)) {
            throw new IllegalStateException("Collection within collection is not supported: method=" + method);
        }

        JpaLinkDefinition<? extends JpaDataNodeDefinition> linkDefinition;
        Any any = method.getAnnotation(Any.class);
        if (any != null) {
            JpaAnyContainerDefinition targetDefinition = new JpaAnyContainerDefinition(jpaClass);
            QName jaxbNameForAny = new QName(any.jaxbNameNamespace(), any.jaxbNameLocalPart());
            linkDefinition = new JpaLinkDefinition<>(jaxbNameForAny, jpaName, collectionSpecification, false,
                    targetDefinition);
        } else if (ObjectReference.class.isAssignableFrom(jpaClass)) {
            boolean embedded = method.isAnnotationPresent(Embedded.class);
            // computing referenced entity type from returned content type like RObjectReference<RFocus> or REmbeddedReference<RRole>
            Class referencedJpaClass;
            if (returnedContentType instanceof ParameterizedType) {
                referencedJpaClass = getClass(
                        ((ParameterizedType) returnedContentType).getActualTypeArguments()[0]);
            } else {
                referencedJpaClass = RObject.class;
            }
            JpaReferenceDefinition targetDefinition = new JpaReferenceDefinition(jpaClass, referencedJpaClass);
            linkDefinition = new JpaLinkDefinition<>(itemPath, jpaName, collectionSpecification, embedded,
                    targetDefinition);
        } else if (isEntity(jpaClass)) {
            JpaEntityDefinition content = parseClass(jpaClass);
            boolean embedded = method.isAnnotationPresent(Embedded.class)
                    || jpaClass.isAnnotationPresent(Embeddable.class);
            linkDefinition = new JpaLinkDefinition<JpaDataNodeDefinition>(itemPath, jpaName,
                    collectionSpecification, embedded, content);
        } else {
            boolean lob = method.isAnnotationPresent(Lob.class);
            boolean enumerated = method.isAnnotationPresent(Enumerated.class);
            //todo implement also lookup for @Table indexes
            boolean indexed = method.isAnnotationPresent(Index.class);
            Class jaxbClass = getJaxbClass(method, jpaClass);

            if (method.isAnnotationPresent(IdQueryProperty.class)) {
                if (collectionSpecification != null) {
                    throw new IllegalStateException(
                            "ID property is not allowed to be multivalued; for method " + method);
                }
                itemPath = new ItemPath(new IdentifierPathSegment());
            } else if (method.isAnnotationPresent(OwnerIdGetter.class)) {
                if (collectionSpecification != null) {
                    throw new IllegalStateException(
                            "Owner ID property is not allowed to be multivalued; for method " + method);
                }
                itemPath = new ItemPath(new ParentPathSegment(), new IdentifierPathSegment());
            }

            JpaPropertyDefinition propertyDefinition = new JpaPropertyDefinition(jpaClass, jaxbClass, lob,
                    enumerated, indexed);
            // Note that properties are considered to be embedded
            linkDefinition = new JpaLinkDefinition<JpaDataNodeDefinition>(itemPath, jpaName,
                    collectionSpecification, true, propertyDefinition);
        }
        return linkDefinition;
    }

    private Class<?> getClass(Type type) {
        if (type instanceof Class) {
            return ((Class) type);
        } else if (type instanceof ParameterizedType) {
            return getClass(((ParameterizedType) type).getRawType());
        } else {
            throw new IllegalStateException("Unsupported type: " + type);
        }
    }

    private void addVirtualDefinitions(Class jpaClass, JpaEntityDefinition entityDef) {
        addVirtualDefinitionsForClass(jpaClass, entityDef);

        while ((jpaClass = jpaClass.getSuperclass()) != null) {
            addVirtualDefinitionsForClass(jpaClass, entityDef);
        }
    }

    private void addVirtualDefinitionsForClass(Class jpaClass, JpaEntityDefinition entityDef) {
        if (!jpaClass.isAnnotationPresent(QueryEntity.class)) {
            return;
        }

        QueryEntity qEntity = (QueryEntity) jpaClass.getAnnotation(QueryEntity.class);

        for (VirtualAny any : qEntity.anyElements()) {
            QName jaxbName = new QName(any.jaxbNameNamespace(), any.jaxbNameLocalPart());
            VirtualAnyContainerDefinition def = new VirtualAnyContainerDefinition(any.ownerType());
            JpaLinkDefinition linkDefinition = new JpaLinkDefinition(jaxbName, null, null, false, def);
            entityDef.addDefinition(linkDefinition);
        }

        for (VirtualCollection collection : qEntity.collections()) {
            // only collections of entities expected at this moment
            VirtualCollectionSpecification colSpec = new VirtualCollectionSpecification(
                    collection.additionalParams());
            QName jaxbName = createQName(collection.jaxbName());
            String jpaName = collection.jpaName();
            JpaEntityDefinition content = parseClass(collection.collectionType());
            JpaLinkDefinition linkDefinition = new JpaLinkDefinition(jaxbName, jpaName, colSpec, false, content);
            entityDef.addDefinition(linkDefinition);
        }

        for (VirtualEntity entity : qEntity.entities()) {
            QName jaxbName = createQName(entity.jaxbName());
            String jpaName = normalizeJpaName(entity.jpaName());
            if (jpaName != null) {
                throw new IllegalStateException(
                        "Only self-pointing virtual entities are supported for now; this one is not: " + jaxbName
                                + " in " + entityDef);
            }
            JpaDataNodeDefinition target = new JpaEntityPointerDefinition(entityDef); // pointer to avoid loops
            JpaLinkDefinition linkDefinition = new JpaLinkDefinition(jaxbName, jpaName, null, false, target);
            entityDef.addDefinition(linkDefinition);
        }
    }

    private QName createQName(JaxbName name) {
        return new QName(name.namespace(), name.localPart());
    }

    private String normalizeJpaName(String name) {
        if (StringUtils.isEmpty(name)) {
            return null; // "" -> null
        } else {
            return name;
        }
    }

    private boolean isEntity(Class type) {
        if (RPolyString.class.isAssignableFrom(type)) {
            //it's hibernate entity but from prism point of view it's property
            return false;
        }
        return type.getAnnotation(Entity.class) != null || type.getAnnotation(Embeddable.class) != null;
    }

    private ItemPath getJaxbName(Method method) {
        if (method.isAnnotationPresent(JaxbName.class)) {
            JaxbName jaxbName = method.getAnnotation(JaxbName.class);
            return new ItemPath(new QName(jaxbName.namespace(), jaxbName.localPart()));
        } else if (method.isAnnotationPresent(JaxbPath.class)) {
            JaxbPath jaxbPath = method.getAnnotation(JaxbPath.class);
            List<QName> names = new ArrayList<>(jaxbPath.itemPath().length);
            for (JaxbName jaxbName : jaxbPath.itemPath()) {
                names.add(new QName(jaxbName.namespace(), jaxbName.localPart()));
            }
            return new ItemPath(names.toArray(new QName[0]));
        } else {
            return new ItemPath(new QName(SchemaConstantsGenerated.NS_COMMON, getPropertyName(method.getName())));
        }
    }

    // second parameter is just to optimize
    private Class getJaxbClass(Method method, Class returnedClass) {
        JaxbType annotation = (JaxbType) method.getAnnotation(JaxbType.class);
        if (annotation != null) {
            return annotation.type();
        }
        Class classFromEntity = getJaxbClassForEntity(returnedClass);
        if (classFromEntity != null) {
            return classFromEntity;
        }
        Package returnedClassPkg = returnedClass.getPackage();
        Package dataObjectsPkg = Marker.class.getPackage();
        if (returnedClassPkg != null && returnedClassPkg.getName().startsWith(dataObjectsPkg.getName())) {
            return null;
        } else {
            return returnedClass; // probably the JAXB value
        }
    }

    private Class getJaxbClassForEntity(Class clazz) {
        if (RObject.class.isAssignableFrom(clazz)) {
            ObjectTypes objectType = ClassMapper.getObjectTypeForHQLType(clazz);
            return objectType.getClassDefinition();
        }
        JaxbType annotation = (JaxbType) clazz.getAnnotation(JaxbType.class);
        if (annotation != null) {
            return annotation.type();
        }
        return null;
    }

    private String getJpaName(Method method) {
        String methodName = method.getName();
        return getPropertyName(methodName);
    }

    private String getPropertyName(String methodName) {
        int startIndex = 3; //method name starts with "get"
        if (methodName.startsWith("is")) {
            startIndex = 2;
        }

        char first = Character.toLowerCase(methodName.charAt(startIndex));
        return Character.toString(first) + StringUtils.substring(methodName, startIndex + 1, methodName.length());
    }
}