/*
* Copyright 2014 - 2017 Blazebit.
*
* 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.blazebit.persistence.impl;
import com.blazebit.annotation.AnnotationUtils;
import com.blazebit.persistence.CTE;
import com.blazebit.persistence.spi.ExtendedQuerySupport;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.BasicType;
import javax.persistence.metamodel.EmbeddableType;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.MapAttribute;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.PluralAttribute;
import javax.persistence.metamodel.Type;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* This is a wrapper around the JPA {@link Metamodel} allows additionally efficient access by other attributes than a Class.
*
* @author Christian Beikov
* @since 1.2
*/
public class EntityMetamodelImpl implements EntityMetamodel {
private final Metamodel delegate;
private final Map<String, EntityType<?>> entityNameMap;
private final Map<String, Class<?>> entityTypes;
private final Map<String, Class<Enum<?>>> enumTypes;
private final Map<Class<?>, Type<?>> classMap;
private final ConcurrentMap<Class<?>, Type<?>> basicTypeMap = new ConcurrentHashMap<>();
private final Map<Class<?>, ManagedType<?>> cteMap;
private final Map<Class<?>, Map<String, Map.Entry<AttributePath, String[]>>> typeAttributeColumnNameMap;
private final Map<Class<?>, Map<String, Map.Entry<AttributePath, String[]>>> typeAttributeColumnTypeMap;
public EntityMetamodelImpl(EntityManagerFactory emf, ExtendedQuerySupport extendedQuerySupport) {
this.delegate = emf.getMetamodel();
Set<ManagedType<?>> managedTypes = delegate.getManagedTypes();
Map<String, EntityType<?>> nameToType = new HashMap<>(managedTypes.size());
Map<String, Class<?>> entityTypes = new HashMap<>(managedTypes.size());
Map<String, Class<Enum<?>>> enumTypes = new HashMap<>(managedTypes.size());
Map<Class<?>, Type<?>> classToType = new HashMap<>(managedTypes.size());
Map<Class<?>, ManagedType<?>> cteToType = new HashMap<>(managedTypes.size());
Map<Class<?>, Map<String, Map.Entry<AttributePath, String[]>>> typeAttributeColumnNames = new HashMap<>(managedTypes.size());
Map<Class<?>, Map<String, Map.Entry<AttributePath, String[]>>> typeAttributeColumnTypeNames = new HashMap<>(managedTypes.size());
EntityManager em = emf.createEntityManager();
Set<Class<?>> seenTypesForEnumResolving = new HashSet<>();
try {
for (ManagedType<?> t : managedTypes) {
if (t instanceof EntityType<?>) {
EntityType<?> e = (EntityType<?>) t;
nameToType.put(e.getName(), e);
entityTypes.put(e.getName(), e.getJavaType());
entityTypes.put(e.getJavaType().getName(), e.getJavaType());
if (extendedQuerySupport != null && extendedQuerySupport.supportsAdvancedSql()) {
Set<Attribute<?, ?>> attributes = (Set<Attribute<?, ?>>) t.getAttributes();
Map<String, Map.Entry<AttributePath, String[]>> attributeMap = new HashMap<>(attributes.size());
typeAttributeColumnNames.put(t.getJavaType(), Collections.unmodifiableMap(attributeMap));
Map<String, Map.Entry<AttributePath, String[]>> attributeTypeMap = new HashMap<>(attributes.size());
typeAttributeColumnTypeNames.put(t.getJavaType(), Collections.unmodifiableMap(attributeTypeMap));
seenTypesForEnumResolving.add(t.getJavaType());
for (Attribute<?, ?> attribute : attributes) {
Class<?> fieldType = JpaUtils.resolveFieldClass(t.getJavaType(), attribute);
if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED) {
collectColumnNames(extendedQuerySupport, em, e, attributeMap, attribute.getName(), delegate.embeddable(fieldType));
}
AttributePath path = new AttributePath(Arrays.<Attribute<?, ?>>asList(attribute), fieldType);
// Collect column names
String[] columnNames = extendedQuerySupport.getColumnNames(em, e, attribute.getName());
attributeMap.put(attribute.getName(), new AbstractMap.SimpleEntry<>(path, columnNames));
// Collect column types
String[] columnTypes = extendedQuerySupport.getColumnTypes(em, e, attribute.getName());
attributeTypeMap.put(attribute.getName(), new AbstractMap.SimpleEntry<>(path, columnTypes));
discoverEnumTypes(seenTypesForEnumResolving, enumTypes, e.getJavaType(), attribute);
}
} else {
discoverEnumTypes(seenTypesForEnumResolving, enumTypes, t);
}
} else {
discoverEnumTypes(seenTypesForEnumResolving, enumTypes, t);
}
classToType.put(t.getJavaType(), t);
if (AnnotationUtils.findAnnotation(t.getJavaType(), CTE.class) != null) {
cteToType.put(t.getJavaType(), t);
}
}
} finally {
em.close();
}
this.entityNameMap = Collections.unmodifiableMap(nameToType);
this.entityTypes = Collections.unmodifiableMap(entityTypes);
this.enumTypes = Collections.unmodifiableMap(enumTypes);
this.classMap = Collections.unmodifiableMap(classToType);
this.cteMap = Collections.unmodifiableMap(cteToType);
this.typeAttributeColumnNameMap = Collections.unmodifiableMap(typeAttributeColumnNames);
this.typeAttributeColumnTypeMap = Collections.unmodifiableMap(typeAttributeColumnTypeNames);
}
private void discoverEnumTypes(Set<Class<?>> seenTypesForEnumResolving, Map<String, Class<Enum<?>>> enumTypes, ManagedType<?> t) {
if (!seenTypesForEnumResolving.add(t.getJavaType())) {
return;
}
for (Attribute<?, ?> attribute : (Set<Attribute<?, ?>>) t.getAttributes()) {
discoverEnumTypes(seenTypesForEnumResolving, enumTypes, t.getJavaType(), attribute);
}
}
private void discoverEnumTypes(Set<Class<?>> seenTypesForEnumResolving, Map<String, Class<Enum<?>>> enumTypes, Type<?> type) {
if (type.getPersistenceType() == Type.PersistenceType.BASIC) {
Class<?> elementType = type.getJavaType();
if (elementType.isEnum()) {
enumTypes.put(elementType.getName(), (Class<Enum<?>>) elementType);
}
} else {
discoverEnumTypes(seenTypesForEnumResolving, enumTypes, (ManagedType<?>) type);
}
}
private void discoverEnumTypes(Set<Class<?>> seenTypesForEnumResolving, Map<String, Class<Enum<?>>> enumTypes, Class<?> baseType, Attribute<?, ?> attribute) {
Class<?> fieldType = JpaUtils.resolveFieldClass(baseType, attribute);
if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.BASIC) {
if (fieldType.isEnum()) {
enumTypes.put(fieldType.getName(), (Class<Enum<?>>) fieldType);
}
} else if (attribute.isCollection()) {
PluralAttribute<?, ?, ?> pluralAttribute = (PluralAttribute<?, ?, ?>) attribute;
if (pluralAttribute.getCollectionType() == PluralAttribute.CollectionType.MAP) {
MapAttribute<?, ?, ?> mapAttribute = (MapAttribute<?, ?, ?>) pluralAttribute;
discoverEnumTypes(seenTypesForEnumResolving, enumTypes, mapAttribute.getKeyType());
}
discoverEnumTypes(seenTypesForEnumResolving, enumTypes, pluralAttribute.getElementType());
} else if (!seenTypesForEnumResolving.contains(fieldType)) {
discoverEnumTypes(seenTypesForEnumResolving, enumTypes, delegate.managedType(fieldType));
}
}
private void collectColumnNames(ExtendedQuerySupport extendedQuerySupport, EntityManager em, EntityType<?> e, Map<String, Map.Entry<AttributePath, String[]>> attributeMap, String parent, EmbeddableType<?> type) {
Set<Attribute<?, ?>> attributes = (Set<Attribute<?, ?>>) type.getAttributes();
for (Attribute<?, ?> attribute : attributes) {
Class<?> fieldType = JpaUtils.resolveFieldClass(type.getJavaType(), attribute);
if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED) {
collectColumnNames(extendedQuerySupport, em, e, attributeMap, parent + "." + attribute.getName(), delegate.embeddable(fieldType));
}
String attributeName = parent + "." + attribute.getName();
String[] columnNames = extendedQuerySupport.getColumnNames(em, e, attributeName);
AttributePath path = new AttributePath(Arrays.<Attribute<?, ?>>asList(attribute), fieldType);
attributeMap.put(attributeName, new AbstractMap.SimpleEntry<AttributePath, String[]>(path, columnNames));
}
}
public Map<String, Map.Entry<AttributePath, String[]>> getAttributeColumnNameMapping(Class<?> cls) {
return typeAttributeColumnNameMap.get(cls);
}
public Map<String, Map.Entry<AttributePath, String[]>> getAttributeColumnTypeMapping(Class<?> cls) {
return typeAttributeColumnTypeMap.get(cls);
}
@Override
public <X> EntityType<X> entity(Class<X> cls) {
return delegate.entity(cls);
}
public EntityType<?> entity(String name) {
EntityType<?> type = entityNameMap.get(name);
if (type == null) {
throw new IllegalArgumentException("Invalid entity type: " + name);
}
return type;
}
@Override
public EntityType<?> getEntity(String name) {
return entityNameMap.get(name);
}
@Override
public ManagedType<?> getManagedType(String name) {
return entityNameMap.get(name);
}
public Map<String, Class<?>> getEntityTypes() {
return entityTypes;
}
public Map<String, Class<Enum<?>>> getEnumTypes() {
return enumTypes;
}
@Override
public <X> ManagedType<X> managedType(Class<X> cls) {
return delegate.managedType(cls);
}
@Override
@SuppressWarnings({ "unchecked" })
public <X> Type<X> type(Class<X> cls) {
Type<?> type = classMap.get(cls);
if (type != null) {
return (Type<X>) type;
}
type = new BasicTypeImpl<>(cls);
Type<?> oldType = basicTypeMap.putIfAbsent(cls, type);
if (oldType != null) {
type = oldType;
}
return (Type<X>) type;
}
@Override
public ManagedType<?> managedType(String name) {
ManagedType<?> t = entityNameMap.get(name);
if (t == null) {
throw new IllegalStateException("Managed type with name '" + name + "' does not exist!");
}
return t;
}
@Override
@SuppressWarnings({ "unchecked" })
public <X> ManagedType<X> getManagedType(Class<X> cls) {
return (ManagedType<X>) classMap.get(cls);
}
@Override
@SuppressWarnings({ "unchecked" })
public <X> EntityType<X> getEntity(Class<X> cls) {
Type<?> type = classMap.get(cls);
if (type == null || !(type instanceof EntityType<?>)) {
return null;
}
return (EntityType<X>) type;
}
@SuppressWarnings({ "unchecked" })
public <X> ManagedType<X> getCte(Class<X> cls) {
return (ManagedType<X>) cteMap.get(cls);
}
@Override
public <X> EmbeddableType<X> embeddable(Class<X> cls) {
return delegate.embeddable(cls);
}
@Override
public Set<ManagedType<?>> getManagedTypes() {
return delegate.getManagedTypes();
}
@Override
public Set<EntityType<?>> getEntities() {
return delegate.getEntities();
}
@Override
public Set<EmbeddableType<?>> getEmbeddables() {
return delegate.getEmbeddables();
}
private static class BasicTypeImpl<T> implements BasicType<T> {
private final Class<T> cls;
public BasicTypeImpl(Class<T> cls) {
this.cls = cls;
}
@Override
public PersistenceType getPersistenceType() {
return PersistenceType.BASIC;
}
@Override
public Class<T> getJavaType() {
return cls;
}
}
}