blob: 593eb71e3d5e220fd39b57c7ec2309f161d8eb1c [file] [log] [blame]
/*
* 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.openjpa.persistence.meta;
import static javax.persistence.metamodel.Type.PersistenceType.BASIC;
import static javax.persistence.metamodel.Type.PersistenceType.EMBEDDABLE;
import static javax.persistence.metamodel.Type.PersistenceType.ENTITY;
import static javax.persistence.metamodel.Type.PersistenceType.MAPPED_SUPERCLASS;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.security.AccessController;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EmbeddableType;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.MappedSuperclassType;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.PluralAttribute.CollectionType;
import javax.persistence.metamodel.StaticMetamodel;
import javax.persistence.metamodel.Type;
import javax.persistence.metamodel.Type.PersistenceType;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.kernel.QueryContext;
import org.apache.openjpa.kernel.exps.AggregateListener;
import org.apache.openjpa.kernel.exps.FilterListener;
import org.apache.openjpa.kernel.exps.Resolver;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.persistence.meta.Members.Member;
import org.apache.openjpa.util.InternalException;
/**
* Adapts JPA Metamodel to OpenJPA meta-data repository.
*
* @author Pinaki Poddar
*
*/
public class MetamodelImpl implements Metamodel, Resolver {
private final MetaDataRepository repos;
private Map<Class<?>, Type<?>> _basics = new HashMap<>();
private Map<Class<?>, EntityType<?>> _entities = new HashMap<>();
private Set<EntityType<?>> _entitiesOnlySet = null;
private Map<Class<?>, EmbeddableType<?>> _embeddables = new HashMap<>();
private Map<Class<?>, MappedSuperclassType<?>> _mappedsupers = new HashMap<>();
private Map<Class<?>, Types.PseudoEntity<?>> _pseudos = new HashMap<>();
private static Localizer _loc = Localizer.forPackage(MetamodelImpl.class);
/**
* Constructs a model with the current content of the supplied non-null repository.
*
*/
public MetamodelImpl(MetaDataRepository repos) {
this.repos = repos;
Collection<Class<?>> classes = repos.loadPersistentTypes(true, null);
for (Class<?> cls : classes) {
if (repos.skipMetadata(cls)) { // AttributeConverters, enums etc....
continue;
}
ClassMetaData meta = repos.getMetaData(cls, null, true);
PersistenceType type = getPersistenceType(meta);
switch (type) {
case ENTITY:
find(cls, _entities, ENTITY, false);
if (meta.isEmbeddable())
find(cls, _embeddables, EMBEDDABLE, false);
break;
case EMBEDDABLE:
find(cls, _embeddables, EMBEDDABLE, false);
break;
case MAPPED_SUPERCLASS:
find(cls, _mappedsupers, MAPPED_SUPERCLASS, false);
break;
default:
}
}
}
public MetaDataRepository getRepository() {
return repos;
}
/**
* Return the metamodel embeddable type representing the embeddable class.
*
* @param cls the type of the represented embeddable class
* @return the metamodel embeddable type
* @throws IllegalArgumentException if not an embeddable class
*/
@Override
public <X> EmbeddableType<X> embeddable(Class<X> clazz) {
return (EmbeddableType<X>)find(clazz, _embeddables, EMBEDDABLE, false);
}
/**
* Return the metamodel entity type representing the entity.
* @param cls the type of the represented entity
* @return the metamodel entity type
* @throws IllegalArgumentException if not an entity
*/
@Override
public <X> EntityType<X> entity(Class<X> clazz) {
return (EntityType<X>) find(clazz, _entities, ENTITY, false);
}
public <X> EntityType<X> entityImpl(Class<X> clazz) {
return (EntityType<X>) find(clazz, _entities, ENTITY, true);
}
/*
* Return the most up-to-date entity only set in the current meta model.
*/
private Collection<EntityType<?>> getEntityValuesOnly() {
if (_entitiesOnlySet == null) {
_entitiesOnlySet = new HashSet<>();
for (Class<?> cls : _entities.keySet()) {
// if key indicates it is a embeddable, do not add to the _entitiesOnlySet.
if (!_embeddables.containsKey(cls)) {
_entitiesOnlySet.add(_entities.get(cls));
}
}
}
return _entitiesOnlySet;
}
/**
* Return the metamodel embeddable types.
* @return the metamodel embeddable types
*/
@Override
public Set<EmbeddableType<?>> getEmbeddables() {
return unmodifiableSet(_embeddables.values());
}
/**
* Return the metamodel entity types.
* @return the metamodel entity types
*/
@Override
public Set<EntityType<?>> getEntities() {
return unmodifiableSet(getEntityValuesOnly());
}
/**
* Return the metamodel managed types.
* @return the metamodel managed types
*/
@Override
public Set<ManagedType<?>> getManagedTypes() {
Set<ManagedType<?>> result = new HashSet<>();
result.addAll(getEntityValuesOnly());
result.addAll(_embeddables.values());
result.addAll(_mappedsupers.values());
return result;
}
/**
* Return the metamodel managed type representing the
* entity, mapped superclass, or embeddable class.
* @param cls the type of the represented managed class
* @return the metamodel managed type
* @throws IllegalArgumentException if not a managed class
*/
@Override
public <X> ManagedType<X> managedType(Class<X> clazz) {
if (_embeddables.containsKey(clazz))
return (EmbeddableType<X>) _embeddables.get(clazz);
if (_entities.containsKey(clazz))
return (EntityType<X>) _entities.get(clazz);
if (_mappedsupers.containsKey(clazz))
return (MappedSuperclassType<X>) _mappedsupers.get(clazz);
throw new IllegalArgumentException(_loc.get("type-not-managed", clazz)
.getMessage());
}
/**
* Return the type representing the basic, entity, mapped superclass, or embeddable class.
* This method differs from {@linkplain #type(Class)} as it also creates a basic or pesudo
* type for the given class argument if not already available in this receiver.
*
* @param cls the type of the represented managed class
* @return the metamodel managed type
* @throws IllegalArgumentException if not a managed class
*/
public <X> Type<X> getType(Class<X> cls) {
try {
return managedType(cls);
} catch (IllegalArgumentException ex) {
if (_basics.containsKey(cls))
return (Type<X>)_basics.get(cls);
if (_pseudos.containsKey(cls))
return (Type<X>)_pseudos.get(cls);
if (java.util.Map.class.isAssignableFrom(cls)) {
Types.PseudoEntity<X> pseudo = new Types.PseudoEntity(cls, this);
_pseudos.put(cls, new Types.PseudoEntity(cls, this));
return pseudo;
} else {
Type<X> basic = new Types.Basic<>(cls);
_basics.put(cls, basic);
return basic;
}
}
}
public static PersistenceType getPersistenceType(ClassMetaData meta) {
if (meta == null)
return BASIC;
if (meta.isAbstract())
return MAPPED_SUPERCLASS;
if (meta.isEmbeddable())
return EMBEDDABLE;
return ENTITY;
}
/**
* Looks up the given container for the managed type representing the given Java class.
* The managed type may become instantiated as a side-effect.
*/
private <V extends ManagedType<?>> V find(Class<?> cls, Map<Class<?>,V> container,
PersistenceType expected, boolean implFind) {
if (container.containsKey(cls)) {
if (implFind || expected != ENTITY || !_embeddables.containsKey(cls)) {
return container.get(cls);
}
}
ClassMetaData meta = repos.getMetaData(cls, null, false);
if (meta != null) {
instantiate(cls, meta, container, expected);
}
return container.get(cls);
}
/**
* Instantiate
* @param <X>
* @param <V>
* @param cls
* @param container
* @param expected
*/
private <X,V extends ManagedType<?>> void instantiate(Class<X> cls, ClassMetaData meta,
Map<Class<?>,V> container, PersistenceType expected) {
PersistenceType actual = getPersistenceType(meta);
if (actual != expected) {
if (!meta.isEmbeddable() || actual != PersistenceType.ENTITY ||
expected != PersistenceType.EMBEDDABLE)
throw new IllegalArgumentException( _loc.get("type-wrong-category",
cls, actual, expected).getMessage());
}
switch (actual) {
case EMBEDDABLE:
Types.Embeddable<X> embedded = new Types.Embeddable<>(meta, this);
_embeddables.put(cls, embedded);
populate(embedded);
// no break : embeddables are stored as both entity and embeddable containers
case ENTITY:
Types.Entity<X> entity = new Types.Entity<>(meta, this);
_entities.put(cls, entity);
_entitiesOnlySet = null;
populate(entity);
break;
case MAPPED_SUPERCLASS:
Types.MappedSuper<X> mapped = new Types.MappedSuper<>(meta, this);
_mappedsupers.put(cls, mapped);
populate(mapped);
break;
default:
throw new InternalException(cls.getName());
}
}
public <T> Set<T> unmodifiableSet(Collection<T> coll) {
HashSet<T> result = new HashSet<>();
for (T t : coll)
result.add(t);
return result;
}
static CollectionType categorizeCollection(Class<?> cls) {
if (Set.class.isAssignableFrom(cls))
return CollectionType.SET;
if (List.class.isAssignableFrom(cls))
return CollectionType.LIST;
if (Collection.class.isAssignableFrom(cls))
return CollectionType.COLLECTION;
if (Map.class.isAssignableFrom(cls))
return CollectionType.MAP;
throw new InternalException(cls.getName() + " not a collection");
}
/**
* Populate the static fields of the canonical type.
*/
public <X> void populate(AbstractManagedType<X> type) {
Class<X> cls = type.getJavaType();
Class<?> mcls = repos.getMetaModel(cls, true);
if (mcls == null)
return;
StaticMetamodel anno = mcls.getAnnotation(StaticMetamodel.class);
if (anno == null)
throw new IllegalArgumentException(_loc.get("meta-class-no-anno",
mcls.getName(), cls.getName(), StaticMetamodel.class.getName()).getMessage());
if (cls != anno.value()) {
throw new IllegalStateException(_loc.get("meta-class-mismatch",
mcls.getName(), cls.getName(), anno.value()).getMessage());
}
ParameterizedType mfType = null;
Attribute<? super X, ?> f = null;
Field[] mfields = AccessController.doPrivileged(J2DoPrivHelper.getDeclaredFieldsAction(mcls));
for (Field mf : mfields) {
try {
mfType = getParameterizedType(mf); // metamodel type
if (mfType == null) {
continue;
}
f = type.getAttribute(mf.getName()); // persistent type
// populate the static field with persistent type information
mf.set(null, f);
} catch (Exception e) {
throw new RuntimeException(_loc.get("meta-field-mismatch",
new Object[] { mf.getName(), mcls.getName(), toTypeName(mfType), f.getJavaType().toString() })
.getMessage(), e);
}
}
}
/**
* Gets the parameterized type of the given field after validating.
*
* @return the field's type as a parameterized type. If the field
* is not parameterized type (that can happen for non-canonical
* metamodel or weaving process introducing synthetic fields),
* returns null.
*/
ParameterizedType getParameterizedType(Field mf) {
java.lang.reflect.Type t = mf.getGenericType();
if (t instanceof ParameterizedType == false) {
repos.getLog().warn(_loc.get("meta-field-not-param",
mf.getDeclaringClass(), mf.getName(), toTypeName(t)).getMessage());
return null;
}
ParameterizedType mfType = (ParameterizedType)t;
java.lang.reflect.Type[] args = mfType.getActualTypeArguments();
if (args.length < 2) {
throw new IllegalStateException(_loc.get("meta-field-less-param",
mf.getDeclaringClass(), mf.getName(), toTypeName(t)).getMessage());
}
return mfType;
}
/**
* Pretty prints a Type.
*/
String toTypeName(java.lang.reflect.Type type) {
if (type instanceof GenericArrayType) {
return toTypeName(((GenericArrayType)type).
getGenericComponentType())+"[]";
}
if (type instanceof ParameterizedType == false) {
Class<?> cls = (Class<?>)type;
return cls.getName();
}
ParameterizedType pType = (ParameterizedType)type;
java.lang.reflect.Type[] args = pType.getActualTypeArguments();
StringBuilder tmp = new StringBuilder(pType.getRawType().toString());
for (int i = 0; i < args.length; i++) {
tmp.append((i == 0) ? '<' : ',');
tmp.append(toTypeName(args[i]));
if (i == args.length-1) tmp.append('>');
}
return tmp.toString();
}
/**
* Validates the given field of the meta class matches the given
* FieldMetaData and
* @param <X>
* @param <Y>
* @param mField
* @param member
*/
void validate(Field metaField, FieldMetaData fmd) {
}
<X,Y> void validate(Field mField, Member<X, Y> member) {
if (!ParameterizedType.class.isInstance(mField.getGenericType())) {
throw new IllegalArgumentException(_loc.get("meta-bad-field",
mField).getMessage());
}
ParameterizedType mfType = (ParameterizedType)mField.getGenericType();
java.lang.reflect.Type[] args = mfType.getActualTypeArguments();
java.lang.reflect.Type owner = args[0];
if (member.getDeclaringType().getJavaType() != owner)
throw new IllegalArgumentException(_loc.get("meta-bad-field-owner",
mField, owner).getMessage());
}
@Override
public Class classForName(String name, String[] imports) {
throw new UnsupportedOperationException();
}
@Override
public AggregateListener getAggregateListener(String tag) {
throw new UnsupportedOperationException();
}
@Override
public OpenJPAConfiguration getConfiguration() {
return repos.getConfiguration();
}
@Override
public FilterListener getFilterListener(String tag) {
throw new UnsupportedOperationException();
}
@Override
public QueryContext getQueryContext() {
throw new UnsupportedOperationException();
}
}