be.shad.tsqb.helper.TypeSafeQueryHelperImpl.java Source code

Java tutorial

Introduction

Here is the source code for be.shad.tsqb.helper.TypeSafeQueryHelperImpl.java

Source

/*
 * Copyright Gert Wijns gert.wijns@gmail.com
 *
 * 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 be.shad.tsqb.helper;

import static be.shad.tsqb.proxy.TypeSafeQueryProxyType.ComponentType;
import static be.shad.tsqb.proxy.TypeSafeQueryProxyType.CompositeType;
import static be.shad.tsqb.proxy.TypeSafeQueryProxyType.EntityCollectionType;
import static be.shad.tsqb.proxy.TypeSafeQueryProxyType.EntityType;
import static be.shad.tsqb.proxy.TypeSafeQueryProxyType.SelectionDtoType;

import java.lang.reflect.Method;

import javax.persistence.EntityManagerFactory;

import org.hibernate.MappingException;
import org.hibernate.SessionFactory;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.metadata.CollectionMetadata;
import org.hibernate.metamodel.spi.MetamodelImplementor;
import org.hibernate.persister.collection.OneToManyPersister;
import org.hibernate.type.BasicType;
import org.hibernate.type.CollectionType;
import org.hibernate.type.ComponentType;
import org.hibernate.type.StringRepresentableType;
import org.hibernate.type.Type;

import be.shad.tsqb.data.TypeSafeQueryProxyData;
import be.shad.tsqb.data.TypeSafeQuerySelectionProxyData;
import be.shad.tsqb.proxy.TypeSafeQueryProxy;
import be.shad.tsqb.proxy.TypeSafeQueryProxyFactory;
import be.shad.tsqb.proxy.TypeSafeQueryProxyType;
import be.shad.tsqb.proxy.TypeSafeQuerySelectionProxy;
import be.shad.tsqb.query.TypeSafeQueryInternal;
import be.shad.tsqb.query.TypeSafeRootQueryInternal;
import be.shad.tsqb.selection.group.TypeSafeQuerySelectionGroup;
import javassist.util.proxy.ProxyObject;

public class TypeSafeQueryHelperImpl implements TypeSafeQueryHelper {
    private static final Integer DEFAULT_INTEGER = 84;
    private static final Double DEFAULT_DOUBLE = DEFAULT_INTEGER.doubleValue();
    private static final Long DEFAULT_LONG = DEFAULT_INTEGER.longValue();
    private static final Byte DEFAULT_BYTE = DEFAULT_INTEGER.byteValue();
    private static final Short DEFAULT_SHORT = DEFAULT_INTEGER.shortValue();
    private static final Float DEFAULT_FLOAT = DEFAULT_INTEGER.floatValue();
    private static final Character DEFAULT_CHAR = 'g';

    private final SessionFactory sessionFactory;
    private final MetamodelImplementor metaModel;
    private final TypeSafeQueryProxyFactory proxyFactory;
    private final ConcreteDtoClassResolver classResolver;

    public TypeSafeQueryHelperImpl(SessionFactory sessionFactory) {
        this(sessionFactory, new ConcreteDtoClassResolverImpl());
    }

    public TypeSafeQueryHelperImpl(SessionFactory sessionFactory, ConcreteDtoClassResolver classResolver) {
        this.sessionFactory = sessionFactory;
        this.metaModel = (MetamodelImplementor) ((EntityManagerFactory) sessionFactory).getMetamodel();
        this.classResolver = classResolver;
        this.proxyFactory = new TypeSafeQueryProxyFactory(classResolver);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ConcreteDtoClassResolver getConcreteDtoClassResolver() {
        return classResolver;
    }

    private Type getTargetType(TypeSafeQueryProxyData data, String property) {
        if (data.getProxyType().isComposite()) {
            return getMetaData(data.getCompositeTypeEntityParent().getPropertyType())
                    .getPropertyType(data.getCompositePropertyPath() + "." + property);
        }
        return getMetaData(data.getPropertyType()).getPropertyType(property);
    }

    /**
     * Retrieves the type information from hibernate.
     */
    private Class<?> getTargetEntityClass(Type propertyType) {
        if (CollectionType.class.isAssignableFrom(propertyType.getClass())) {
            CollectionType collectionType = (CollectionType) propertyType;
            Type elementType = collectionType.getElementType((SessionFactoryImplementor) sessionFactory);
            return elementType.getReturnedClass();
        }
        return propertyType.getReturnedClass();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getEntityName(Class<?> entityClass) {
        return getMetaData(entityClass).getEntityName();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> T createTypeSafeSelectProxy(final TypeSafeRootQueryInternal query, Class<T> clazz,
            TypeSafeQuerySelectionGroup group) {
        final T proxy = proxyFactory.getProxy(clazz, SelectionDtoType);
        TypeSafeQuerySelectionProxyData data = query.getDataTree().createSelectionData(null, null, clazz, group,
                (TypeSafeQuerySelectionProxy) proxy);
        setSelectionDtoMethodHandler(query, data);
        query.getProjections().setResultClass(clazz);
        return proxy;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public TypeSafeQuerySelectionProxyData createTypeSafeSelectSubProxy(TypeSafeRootQueryInternal query,
            TypeSafeQuerySelectionProxyData parent, String propertyName, Class<?> targetClass, boolean setter) {
        TypeSafeQuerySelectionProxyData childData = query.getDataTree().createSelectionData(parent, propertyName,
                targetClass, parent.getGroup(), null);
        if (!setter && !java.util.Collection.class.isAssignableFrom(targetClass)) {
            // only need method handler when the data is being retrieved,
            // on set we will just return the proxy data.
            setSelectionDtoMethodHandler(query, childData);
        }
        return childData;
    }

    /**
     * Build nested property path when values are retrieved, link to projections when values are set.
     */
    void setSelectionDtoMethodHandler(final TypeSafeRootQueryInternal query,
            final TypeSafeQuerySelectionProxyData data) {
        if (data.getProxy() == null) {
            TypeSafeQuerySelectionProxy childProxy = null;
            if (!isBasicType(data.getPropertyType())) {
                childProxy = (TypeSafeQuerySelectionProxy) proxyFactory.getProxy(data.getPropertyType(),
                        SelectionDtoType);
                data.setProxy(childProxy);
            } else {
                // No proxy is to be set if it is a basic type, these types
                // won't need to get extra method handling.
                return;
            }
        }
        ((ProxyObject) data.getProxy()).setHandler(new SelectionDtoMethodHandler(this, query, data));
    }

    boolean isBasicType(Class<?> returnType) {
        return sessionFactory.getTypeHelper().basic(returnType) != null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <S, T extends S> T createTypeSafeSubtypeProxy(TypeSafeQueryInternal query, S proxy, Class<T> subtype) {
        if (!(proxy instanceof TypeSafeQueryProxy)) {
            throw new IllegalArgumentException(
                    String.format("The provided proxy [%s] is not a TypeSafeQueryProxy.", proxy));
        }
        ClassMetadata subtypeMeta = getMetaData(subtype);
        if (subtypeMeta == null) {
            throw new IllegalArgumentException(String.format(
                    "The subtype [%s] is not " + "known in hibernate. Maybe you forgot to map it?.", subtype));
        }
        // we now know the subtype is a hibernate type and it should be a subclass of the proxy,
        // bind the same data object to the subtype:
        T subtypeProxy = proxyFactory.getProxy(subtype, EntityType);
        TypeSafeQueryProxyData data = ((TypeSafeQueryProxy) proxy).getTypeSafeProxyData();
        setEntityProxyMethodListener(query, (TypeSafeQueryProxy) subtypeProxy, data);
        return subtypeProxy;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> T createTypeSafeFromProxy(TypeSafeQueryInternal query, Class<T> clazz) {
        T proxy = proxyFactory.getProxy(clazz, EntityType);
        TypeSafeQueryProxyData data = query.getDataTree().createData(null, null, clazz, EntityType, null,
                (TypeSafeQueryProxy) proxy);
        setEntityProxyMethodListener(query, (TypeSafeQueryProxy) proxy, data);
        return proxy;
    }

    /**
     * Sets the method handler on the proxy to create new proxies when
     * hibernate entities are traversed via the getter/setters.
     */
    private void setEntityProxyMethodListener(final TypeSafeQueryInternal query, final TypeSafeQueryProxy proxy,
            final TypeSafeQueryProxyData data) {
        ((ProxyObject) proxy).setHandler(new EntityProxyMethodHandler(this, query, data));
    }

    /**
     * Creates data based on the hibernate metadata for the given <code>property</code>.
     */
    TypeSafeQueryProxyData createChildData(TypeSafeQueryInternal query, TypeSafeQueryProxyData parent,
            String property) {
        Type propertyType = getTargetType(parent, property);
        Class<?> targetClass = getTargetEntityClass(propertyType);
        ClassMetadata metadata = getMetaData(targetClass);
        if (metadata == null && !propertyType.isComponentType()) {
            return query.getDataTree().createData(parent, property, targetClass);
        }
        TypeSafeQueryProxyType proxyType = null;
        if (metadata != null) {
            proxyType = propertyType.isCollectionType() ? EntityCollectionType : EntityType;
        } else {
            proxyType = propertyType instanceof ComponentType ? ComponentType : CompositeType;
        }
        TypeSafeQueryProxy proxy = (TypeSafeQueryProxy) proxyFactory.getProxy(targetClass, proxyType);
        TypeSafeQueryProxyData data = query.getDataTree().createData(parent, property, targetClass, proxyType,
                metadata == null ? null : metadata.getIdentifierPropertyName(), proxy);
        setEntityProxyMethodListener(query, proxy, data);
        return data;
    }

    private ClassMetadata getMetaData(Class<?> targetClass) {
        try {
            return (ClassMetadata) metaModel.entityPersister(targetClass);
        } catch (MappingException ex) {
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public TypeSafeQueryProxyData createTypeSafeJoinProxy(TypeSafeQueryInternal query,
            TypeSafeQueryProxyData parent, String propertyName, Class<?> targetClass) {
        if (propertyName != null) {
            return createChildData(query, parent, propertyName);
        } else {
            return createClassJoinProxy(query, parent, targetClass);
        }
    }

    /**
     * {@inheritDoc}
     */
    private TypeSafeQueryProxyData createClassJoinProxy(TypeSafeQueryInternal query, TypeSafeQueryProxyData parent,
            Class<?> targetClass) {
        ClassMetadata metadata = getMetaData(targetClass);
        TypeSafeQueryProxyType proxyType = TypeSafeQueryProxyType.EntityType;
        TypeSafeQueryProxy proxy = (TypeSafeQueryProxy) proxyFactory.getProxy(targetClass, proxyType);
        TypeSafeQueryProxyData data = query.getDataTree().createData(parent, null, targetClass, proxyType,
                metadata.getIdentifierPropertyName(), proxy);
        setEntityProxyMethodListener(query, proxy, data);
        return data;
    }

    /**
     * Simple conversion to the property path to be used in the query building phase.
     */
    String method2PropertyName(Method m) {
        String name = m.getName();
        int start;
        if (name.startsWith("get")) {
            start = 3;
        } else if (name.startsWith("is")) {
            start = 2;
        } else if (name.startsWith("set")) {
            start = 3;
        } else {
            return name;
        }
        String ret = name.substring(start, ++start).toLowerCase();
        if (name.length() > start) {
            ret += name.substring(start);
        }
        return ret;
    }

    /**
     *
     */
    @Override
    public String getMappedByProperty(TypeSafeQueryProxyData child) {
        Type propertyType = getTargetType(child.getParent(), child.getPropertyPath());
        if (!propertyType.isCollectionType()) {
            throw new IllegalArgumentException("Method not designed to fetch MappedByProperty "
                    + "for a non-collection type. PropertyType was: " + propertyType);
        }

        CollectionMetadata collectionMetadata = (CollectionMetadata) metaModel
                .collectionPersister(((CollectionType) propertyType).getRole());
        if (collectionMetadata instanceof OneToManyPersister) {
            OneToManyPersister persister = (OneToManyPersister) collectionMetadata;
            return persister.getMappedByProperty();
        }
        // what about many to many?
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    @Override
    public String toLiteral(Object value) {
        if (value == null) {
            return "null";
        }
        BasicType basic = sessionFactory.getTypeHelper().basic(value.getClass());
        if (basic instanceof StringRepresentableType<?>) {
            String literal = ((StringRepresentableType<Object>) basic).toString(value);
            if (value instanceof Number || value instanceof Boolean) {
                return literal;
            }
            return "'" + literal + "'";
        } else {
            throw new IllegalArgumentException("Failed to convert: " + value);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getResolvedTypeName(Class<?> javaType) {
        return sessionFactory.getTypeHelper().basic(javaType).getName();
    }

    /**
     * return a random value, (but take primitives into account to prevent NPEs)
     */
    @SuppressWarnings("unchecked")
    public <T> T getDummyValue(Class<T> clazz) {
        Class<?> primitiveClass = getPrimitiveClass(clazz);
        if (primitiveClass != null) {
            return (T) defaultPrimitiveValue(primitiveClass);
        }
        return null;
    }

    /**
     * @return the primitive class if the class could be a primitive.
     */
    private Class<?> getPrimitiveClass(Class<?> valueClass) {
        if (valueClass.isPrimitive()) {
            return valueClass;
        } else if (Boolean.class.equals(valueClass)) {
            return Boolean.TYPE;
        } else if (Integer.class.equals(valueClass)) {
            return Integer.TYPE;
        } else if (Long.class.equals(valueClass)) {
            return Long.TYPE;
        } else if (Double.class.equals(valueClass)) {
            return Double.TYPE;
        } else if (Byte.class.equals(valueClass)) {
            return Byte.TYPE;
        } else if (Short.class.equals(valueClass)) {
            return Short.TYPE;
        } else if (Float.class.equals(valueClass)) {
            return Float.TYPE;
        } else if (Character.class.equals(valueClass)) {
            return Character.TYPE;
        }
        return null;
    }

    /**
     * @return a default value for each primitive class.
     */
    private Object defaultPrimitiveValue(Class<?> primitiveClass) {
        if (primitiveClass == Boolean.TYPE) {
            return Boolean.FALSE;
        } else if (primitiveClass == Integer.TYPE) {
            return DEFAULT_INTEGER;
        } else if (primitiveClass == Long.TYPE) {
            return DEFAULT_LONG;
        } else if (primitiveClass == Double.TYPE) {
            return DEFAULT_DOUBLE;
        } else if (primitiveClass == Byte.TYPE) {
            return DEFAULT_BYTE;
        } else if (primitiveClass == Short.TYPE) {
            return DEFAULT_SHORT;
        } else if (primitiveClass == Float.TYPE) {
            return DEFAULT_FLOAT;
        } else if (primitiveClass == Character.TYPE) {
            return DEFAULT_CHAR;
        }
        throw new IllegalArgumentException("Didn't take a primtiive class into account: " + primitiveClass);
    }
}