blob: 9a61b8e27384682c22a94118915526663d27de57 [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;
import static javax.persistence.GenerationType.AUTO;
import static org.apache.openjpa.persistence.MetaDataTag.ACCESS;
import static org.apache.openjpa.persistence.MetaDataTag.CACHEABLE;
import static org.apache.openjpa.persistence.MetaDataTag.DATASTORE_ID;
import static org.apache.openjpa.persistence.MetaDataTag.DATA_CACHE;
import static org.apache.openjpa.persistence.MetaDataTag.DEPENDENT;
import static org.apache.openjpa.persistence.MetaDataTag.DETACHED_STATE;
import static org.apache.openjpa.persistence.MetaDataTag.ELEM_DEPENDENT;
import static org.apache.openjpa.persistence.MetaDataTag.ELEM_TYPE;
import static org.apache.openjpa.persistence.MetaDataTag.EMBEDDED_ID;
import static org.apache.openjpa.persistence.MetaDataTag.ENTITY_LISTENERS;
import static org.apache.openjpa.persistence.MetaDataTag.EXCLUDE_DEFAULT_LISTENERS;
import static org.apache.openjpa.persistence.MetaDataTag.EXCLUDE_SUPERCLASS_LISTENERS;
import static org.apache.openjpa.persistence.MetaDataTag.EXTERNALIZER;
import static org.apache.openjpa.persistence.MetaDataTag.EXTERNAL_VALS;
import static org.apache.openjpa.persistence.MetaDataTag.FACTORY;
import static org.apache.openjpa.persistence.MetaDataTag.FETCH_GROUP;
import static org.apache.openjpa.persistence.MetaDataTag.FETCH_GROUPS;
import static org.apache.openjpa.persistence.MetaDataTag.FLUSH_MODE;
import static org.apache.openjpa.persistence.MetaDataTag.GENERATED_VALUE;
import static org.apache.openjpa.persistence.MetaDataTag.ID;
import static org.apache.openjpa.persistence.MetaDataTag.ID_CLASS;
import static org.apache.openjpa.persistence.MetaDataTag.INVERSE_LOGICAL;
import static org.apache.openjpa.persistence.MetaDataTag.KEY_DEPENDENT;
import static org.apache.openjpa.persistence.MetaDataTag.KEY_TYPE;
import static org.apache.openjpa.persistence.MetaDataTag.LOAD_FETCH_GROUP;
import static org.apache.openjpa.persistence.MetaDataTag.LRS;
import static org.apache.openjpa.persistence.MetaDataTag.MANAGED_INTERFACE;
import static org.apache.openjpa.persistence.MetaDataTag.MAPPED_BY_ID;
import static org.apache.openjpa.persistence.MetaDataTag.MAP_KEY;
import static org.apache.openjpa.persistence.MetaDataTag.MAP_KEY_CLASS;
import static org.apache.openjpa.persistence.MetaDataTag.NATIVE_QUERIES;
import static org.apache.openjpa.persistence.MetaDataTag.NATIVE_QUERY;
import static org.apache.openjpa.persistence.MetaDataTag.ORDER_BY;
import static org.apache.openjpa.persistence.MetaDataTag.POST_LOAD;
import static org.apache.openjpa.persistence.MetaDataTag.POST_PERSIST;
import static org.apache.openjpa.persistence.MetaDataTag.POST_REMOVE;
import static org.apache.openjpa.persistence.MetaDataTag.POST_UPDATE;
import static org.apache.openjpa.persistence.MetaDataTag.PRE_PERSIST;
import static org.apache.openjpa.persistence.MetaDataTag.PRE_REMOVE;
import static org.apache.openjpa.persistence.MetaDataTag.PRE_UPDATE;
import static org.apache.openjpa.persistence.MetaDataTag.QUERIES;
import static org.apache.openjpa.persistence.MetaDataTag.QUERY;
import static org.apache.openjpa.persistence.MetaDataTag.READ_ONLY;
import static org.apache.openjpa.persistence.MetaDataTag.SEQ_GENERATOR;
import static org.apache.openjpa.persistence.MetaDataTag.STOREDPROCEDURE_QUERIES;
import static org.apache.openjpa.persistence.MetaDataTag.STOREDPROCEDURE_QUERY;
import static org.apache.openjpa.persistence.MetaDataTag.TYPE;
import static org.apache.openjpa.persistence.MetaDataTag.VERSION;
import java.io.File;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Basic;
import javax.persistence.Cacheable;
import javax.persistence.CascadeType;
import javax.persistence.ElementCollection;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.ExcludeDefaultListeners;
import javax.persistence.ExcludeSuperclassListeners;
import javax.persistence.FetchType;
import javax.persistence.FlushModeType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Lob;
import javax.persistence.LockModeType;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.MapKey;
import javax.persistence.MapKeyClass;
import javax.persistence.MappedSuperclass;
import javax.persistence.MapsId;
import javax.persistence.NamedNativeQueries;
import javax.persistence.NamedNativeQuery;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.NamedStoredProcedureQueries;
import javax.persistence.NamedStoredProcedureQuery;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.OrderBy;
import javax.persistence.ParameterMode;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;
import javax.persistence.QueryHint;
import javax.persistence.SequenceGenerator;
import javax.persistence.StoredProcedureParameter;
import javax.persistence.Version;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.event.BeanLifecycleCallbacks;
import org.apache.openjpa.event.LifecycleCallbacks;
import org.apache.openjpa.event.LifecycleEvent;
import org.apache.openjpa.event.MethodLifecycleCallbacks;
import org.apache.openjpa.kernel.QueryLanguages;
import org.apache.openjpa.kernel.jpql.JPQLParser;
import org.apache.openjpa.lib.conf.Configurations;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.meta.SourceTracker;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.meta.AccessCode;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.DelegatingMetaDataFactory;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.LifecycleMetaData;
import org.apache.openjpa.meta.MetaDataFactory;
import org.apache.openjpa.meta.MetaDataModes;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.meta.MultiQueryMetaData;
import org.apache.openjpa.meta.Order;
import org.apache.openjpa.meta.QueryMetaData;
import org.apache.openjpa.meta.SequenceMetaData;
import org.apache.openjpa.meta.UpdateStrategies;
import org.apache.openjpa.meta.ValueMetaData;
import org.apache.openjpa.meta.ValueStrategies;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.MetaDataException;
import org.apache.openjpa.util.UnsupportedException;
import org.apache.openjpa.util.UserException;
/**
* Persistence annotation metadata parser. Currently does not parse
* deployment descriptors.
*
* @author Abe White
* @author Steve Kim
*/
public class AnnotationPersistenceMetaDataParser
implements MetaDataModes {
private static final Localizer _loc = Localizer.forPackage
(AnnotationPersistenceMetaDataParser.class);
private static final Map<Class<?>, MetaDataTag> _tags =
new HashMap<>();
static {
_tags.put(Access.class, ACCESS);
_tags.put(Cacheable.class, CACHEABLE);
_tags.put(EmbeddedId.class, EMBEDDED_ID);
_tags.put(EntityListeners.class, ENTITY_LISTENERS);
_tags.put(ExcludeDefaultListeners.class, EXCLUDE_DEFAULT_LISTENERS);
_tags.put(ExcludeSuperclassListeners.class,
EXCLUDE_SUPERCLASS_LISTENERS);
_tags.put(FlushModeType.class, FLUSH_MODE);
_tags.put(GeneratedValue.class, GENERATED_VALUE);
_tags.put(Id.class, ID);
_tags.put(IdClass.class, ID_CLASS);
_tags.put(MapKey.class, MAP_KEY);
_tags.put(MapKeyClass.class, MAP_KEY_CLASS);
_tags.put(MapsId.class, MAPPED_BY_ID);
_tags.put(NamedNativeQueries.class, NATIVE_QUERIES);
_tags.put(NamedNativeQuery.class, NATIVE_QUERY);
_tags.put(NamedStoredProcedureQueries.class, STOREDPROCEDURE_QUERIES);
_tags.put(NamedStoredProcedureQuery.class, STOREDPROCEDURE_QUERY);
_tags.put(NamedQueries.class, QUERIES);
_tags.put(NamedQuery.class, QUERY);
_tags.put(OrderBy.class, ORDER_BY);
_tags.put(PostLoad.class, POST_LOAD);
_tags.put(PostPersist.class, POST_PERSIST);
_tags.put(PostRemove.class, POST_REMOVE);
_tags.put(PostUpdate.class, POST_UPDATE);
_tags.put(PrePersist.class, PRE_PERSIST);
_tags.put(PreRemove.class, PRE_REMOVE);
_tags.put(PreUpdate.class, PRE_UPDATE);
_tags.put(SequenceGenerator.class, SEQ_GENERATOR);
_tags.put(Version.class, VERSION);
_tags.put(DataCache.class, DATA_CACHE);
_tags.put(DataStoreId.class, DATASTORE_ID);
_tags.put(Dependent.class, DEPENDENT);
_tags.put(DetachedState.class, DETACHED_STATE);
_tags.put(ElementDependent.class, ELEM_DEPENDENT);
_tags.put(ElementType.class, ELEM_TYPE);
_tags.put(ExternalValues.class, EXTERNAL_VALS);
_tags.put(Externalizer.class, EXTERNALIZER);
_tags.put(Factory.class, FACTORY);
_tags.put(FetchGroup.class, FETCH_GROUP);
_tags.put(FetchGroups.class, FETCH_GROUPS);
_tags.put(InverseLogical.class, INVERSE_LOGICAL);
_tags.put(KeyDependent.class, KEY_DEPENDENT);
_tags.put(KeyType.class, KEY_TYPE);
_tags.put(LoadFetchGroup.class, LOAD_FETCH_GROUP);
_tags.put(LRS.class, LRS);
_tags.put(ManagedInterface.class, MANAGED_INTERFACE);
_tags.put(ReadOnly.class, READ_ONLY);
_tags.put(Type.class, TYPE);
}
private final OpenJPAConfiguration _conf;
private final Log _log;
private MetaDataRepository _repos = null;
private ClassLoader _envLoader = null;
private boolean _override = false;
private int _mode = MODE_NONE;
// packages and their parse modes
private final Map<Package, Integer> _pkgs = new HashMap<>();
// the class we were invoked to parse
protected Class<?> _cls = null;
protected Stack<Class<?>> _stack = new Stack<>();
private File _file = null;
/**
* Constructor; supply configuration.
*/
public AnnotationPersistenceMetaDataParser(OpenJPAConfiguration conf) {
_conf = conf;
_log = conf.getLog(OpenJPAConfiguration.LOG_METADATA);
}
/**
* Configuration supplied on construction.
*/
public OpenJPAConfiguration getConfiguration() {
return _conf;
}
/**
* Metadata log.
*/
public Log getLog() {
return _log;
}
/**
* Returns the repository for this parser. If none has been set,
* create a new repository and sets it.
*/
public MetaDataRepository getRepository() {
if (_repos == null) {
MetaDataRepository repos = _conf.newMetaDataRepositoryInstance();
MetaDataFactory mdf = repos.getMetaDataFactory();
if (mdf instanceof DelegatingMetaDataFactory)
mdf = ((DelegatingMetaDataFactory) mdf).getInnermostDelegate();
if (mdf instanceof PersistenceMetaDataFactory)
((PersistenceMetaDataFactory) mdf).setAnnotationParser(this);
_repos = repos;
}
return _repos;
}
/**
* Set the metadata repository for this parser.
*/
public void setRepository(MetaDataRepository repos) {
_repos = repos;
}
/**
* Return the environmental class loader to pass on to parsed
* metadata instances.
*/
public ClassLoader getEnvClassLoader() {
return _envLoader;
}
/**
* Set the environmental class loader to pass on to parsed
* metadata instances.
*/
public void setEnvClassLoader(ClassLoader loader) {
_envLoader = loader;
}
/**
* Whether to allow later parses of mapping information to override
* earlier information for the same class. Defaults to false. Useful
* when a tool is mapping a class, so that annotation partial mapping
* information can be used even when mappings are stored in another
* location.
*/
public boolean getMappingOverride() {
return _override;
}
/**
* Whether to allow later parses of mapping information to override
* earlier information for the same class. Defaults to false. Useful
* when a tool is mapping a class, so that annotation partial mapping
* information can be used even when mappings are stored in another
* location.
*/
public void setMappingOverride(boolean override) {
_override = override;
}
/**
* The parse mode.
*/
public int getMode() {
return _mode;
}
/**
* The parse mode.
*/
public void setMode(int mode, boolean on) {
if (mode == MODE_NONE)
_mode = MODE_NONE;
else if (on)
_mode |= mode;
else
_mode &= ~mode;
}
/**
* The parse mode.
*/
public void setMode(int mode) {
_mode = mode;
}
/**
* Convenience method for interpreting {@link #getMode}.
*/
protected boolean isMetaDataMode() {
return (_mode & MODE_META) != 0;
}
/**
* Convenience method for interpreting {@link #getMode}.
*/
protected boolean isQueryMode() {
return (_mode & MODE_QUERY) != 0;
}
/**
* Convenience method for interpreting {@link #getMode}.
*/
protected boolean isMappingMode() {
return (_mode & MODE_MAPPING) != 0;
}
/**
* Returns true if we're in mapping mode or in metadata mode with
* mapping overide enabled.
*/
protected boolean isMappingOverrideMode() {
return isMappingMode() || (_override && isMetaDataMode());
}
/**
* Clear caches.
*/
public void clear() {
_stack.clear();
_cls = null;
_file = null;
_pkgs.clear();
}
/**
* Parse persistence metadata for the given class.
*/
public void parse(Class<?> cls) {
if (_log.isTraceEnabled())
_log.trace(_loc.get("parse-class", cls.getName()));
_cls = cls;
_stack.push(cls);
try {
parsePackageAnnotations();
ClassMetaData meta = parseClassAnnotations();
updateSourceMode(meta);
} finally {
_stack.pop();
_cls = _stack.isEmpty() ? null : _stack.peek();
_file = null;
}
}
/**
* Update the source mode to the class package and class to indicate that
* we've fully parsed them.
*/
private void updateSourceMode(ClassMetaData meta) {
if (_cls.getPackage() != null)
addSourceMode(_cls.getPackage(), _mode);
if (meta != null)
meta.setSourceMode(_mode, true);
}
/**
* Parse information in package-level class annotations.
*/
private void parsePackageAnnotations() {
Package pkg = _cls.getPackage();
if (pkg == null)
return;
int pkgMode = getSourceMode(pkg);
if (pkgMode == 0 && _log.isTraceEnabled())
_log.trace(_loc.get("parse-package", _cls.getName()));
if ((pkgMode & _mode) == _mode) // already visited
return;
MetaDataTag tag;
for (Annotation anno : pkg.getDeclaredAnnotations()) {
tag = _tags.get(anno.annotationType());
if (tag == null) {
handleUnknownPackageAnnotation(pkg, anno);
continue;
}
switch (tag) {
case NATIVE_QUERIES:
if (isQueryMode() && (pkgMode & MODE_QUERY) == 0)
parseNamedNativeQueries(pkg,
((NamedNativeQueries) anno).value());
break;
case NATIVE_QUERY:
if (isQueryMode() && (pkgMode & MODE_QUERY) == 0)
parseNamedNativeQueries(pkg, (NamedNativeQuery) anno);
break;
case QUERIES:
if (isQueryMode() && (pkgMode & MODE_QUERY) == 0)
parseNamedQueries(pkg, ((NamedQueries) anno).value());
break;
case QUERY:
if (isQueryMode() && (pkgMode & MODE_QUERY) == 0)
parseNamedQueries(pkg, (NamedQuery) anno);
break;
case STOREDPROCEDURE_QUERIES:
if (isQueryMode())
parseNamedStoredProcedureQueries(pkg, ((NamedStoredProcedureQueries) anno).value());
break;
case STOREDPROCEDURE_QUERY:
if (isQueryMode())
parseNamedStoredProcedureQueries(pkg, ((NamedStoredProcedureQuery) anno));
break;
case SEQ_GENERATOR:
if (isMappingOverrideMode() &&
(pkgMode & MODE_MAPPING) == 0)
parseSequenceGenerator(pkg, (SequenceGenerator) anno);
break;
default:
throw new UnsupportedException(_loc.get("unsupported", pkg,
anno.toString()));
}
}
// always parse mapping stuff after metadata stuff, in case there are
// dependencies on metadata
if (isMappingOverrideMode() && (pkgMode & MODE_MAPPING) == 0)
parsePackageMappingAnnotations(pkg);
}
/**
* Parse package mapping annotations.
*/
protected void parsePackageMappingAnnotations(Package pkg) {
}
/**
* Allow subclasses to handle unknown annotations.
*/
protected boolean handleUnknownPackageAnnotation(Package pkg,
Annotation anno) {
return false;
}
/**
* The source mode for the given package.
*/
private int getSourceMode(Package pkg) {
Number num = _pkgs.get(pkg);
return (num == null) ? 0 : num.intValue();
}
/**
* Add to the source mode for the given package.
*/
private void addSourceMode(Package pkg, int mode) {
Integer num = _pkgs.get(pkg);
if (num == null)
num = mode;
else
num = num | mode;
_pkgs.put(pkg, num);
}
/**
* Read annotations for the current type.
*/
private ClassMetaData parseClassAnnotations() {
// Check to see if there is cached metadata for the class that we are currently parsing. It
// is possible that one of the annotations (Entity, Embeddable, MappedSuperclass) is in the
// orm.xml. We still need to look at these files for other annotations and more importantly
// setup defaults (ie: Basic fields).
ClassMetaData m = getRepository().getCachedMetaData(_cls);
if (m == null) {
if (!AccessController.doPrivileged(J2DoPrivHelper.isAnnotationPresentAction(_cls, Entity.class))
&& !AccessController.doPrivileged(J2DoPrivHelper.isAnnotationPresentAction(_cls, Embeddable.class))
&& !AccessController.doPrivileged(J2DoPrivHelper.isAnnotationPresentAction(_cls,
MappedSuperclass.class)))
return null;
}
// find / create metadata
ClassMetaData meta = (m == null) ? getMetaData() : m;
if (meta == null)
return null;
Entity entity = _cls.getAnnotation(Entity.class);
MappedSuperclass mapped = _cls.getAnnotation(MappedSuperclass.class);
Embeddable embeddable = _cls.getAnnotation(Embeddable.class);
if (isMetaDataMode()) {
meta.setAbstract(mapped != null);
if (embeddable != null) meta.setEmbeddable();
// while the spec only provides for embedded exclusive, it doesn't
// seem hard to support otherwise
if (entity == null)
meta.setEmbeddedOnly(true);
else {
meta.setEmbeddedOnly(false);
if (!StringUtil.isEmpty(entity.name()))
meta.setTypeAlias(entity.name());
}
}
// track fetch groups to parse them after fields, since they
// rely on field metadata
FetchGroup[] fgs = null;
DetachedState detached = null;
// track listeners since we need to merge them with entity callbacks
Collection<LifecycleCallbacks>[] listeners = null;
MetaDataTag tag;
for (Annotation anno : _cls.getDeclaredAnnotations()) {
tag = _tags.get(anno.annotationType());
if (tag == null) {
handleUnknownClassAnnotation(meta, anno);
continue;
}
switch (tag) {
case ENTITY_LISTENERS:
if (isMetaDataMode())
listeners = parseEntityListeners(meta,
(EntityListeners) anno);
break;
case EXCLUDE_DEFAULT_LISTENERS:
if (isMetaDataMode())
meta.getLifecycleMetaData()
.setIgnoreSystemListeners(true);
break;
case EXCLUDE_SUPERCLASS_LISTENERS:
if (isMetaDataMode())
meta.getLifecycleMetaData().setIgnoreSuperclassCallbacks
(LifecycleMetaData.IGNORE_HIGH);
break;
case FLUSH_MODE:
if (isMetaDataMode())
warnFlushMode(meta);
break;
case ID_CLASS:
if (isMetaDataMode()) {
Class<?> idClass = ((IdClass)anno).value();
if (!Serializable.class.isAssignableFrom(idClass)) {
_log.warn(_loc.get("id-class-not-serializable", idClass, _cls).toString());
}
meta.setObjectIdType(((IdClass) anno).value(), true);
}
break;
case NATIVE_QUERIES:
if (isQueryMode() && (meta.getSourceMode() & MODE_QUERY)==0)
parseNamedNativeQueries(_cls,
((NamedNativeQueries) anno).value());
break;
case NATIVE_QUERY:
if (isQueryMode() && (meta.getSourceMode() & MODE_QUERY)==0)
parseNamedNativeQueries(_cls, (NamedNativeQuery) anno);
break;
case QUERIES:
if (isQueryMode() && (meta.getSourceMode() & MODE_QUERY)==0)
parseNamedQueries(_cls, ((NamedQueries) anno).value());
break;
case QUERY:
if (isQueryMode() && (meta.getSourceMode() & MODE_QUERY)==0)
parseNamedQueries(_cls, (NamedQuery) anno);
break;
case STOREDPROCEDURE_QUERIES:
if (isQueryMode())
parseNamedStoredProcedureQueries(_cls, ((NamedStoredProcedureQueries) anno).value());
break;
case STOREDPROCEDURE_QUERY:
if (isQueryMode())
parseNamedStoredProcedureQueries(_cls, ((NamedStoredProcedureQuery) anno));
break;
case SEQ_GENERATOR:
if (isMappingOverrideMode())
parseSequenceGenerator(_cls, (SequenceGenerator) anno);
break;
case DATA_CACHE:
if (isMetaDataMode())
parseDataCache(meta, (DataCache) anno);
break;
case DATASTORE_ID:
if (isMetaDataMode())
parseDataStoreId(meta, (DataStoreId) anno);
break;
case DETACHED_STATE:
detached = (DetachedState) anno;
break;
case FETCH_GROUP:
if (isMetaDataMode())
fgs = new FetchGroup[]{ (FetchGroup) anno };
break;
case FETCH_GROUPS:
if (isMetaDataMode())
fgs = ((FetchGroups) anno).value();
break;
case MANAGED_INTERFACE:
if (isMetaDataMode())
parseManagedInterface(meta, (ManagedInterface) anno);
break;
case ACCESS:
if (isMetaDataMode())
parseAccess(meta, (Access)anno);
break;
case CACHEABLE:
if (isMetaDataMode()) {
parseCache(meta, (Cacheable) anno);
}
break;
default:
throw new UnsupportedException(_loc.get("unsupported", _cls,
anno.toString()));
}
}
if (isMetaDataMode()) {
parseDetachedState(meta, detached);
// merge callback methods with declared listeners
int[] highs = null;
if (listeners != null) {
highs = new int[listeners.length];
for (int i = 0; i < listeners.length; i++)
if (listeners[i] != null)
highs[i] = listeners[i].size();
}
recordCallbacks(meta, parseCallbackMethods(_cls, listeners, false,
false, getRepository()), highs, false);
// scan possibly non-PC hierarchy for callbacks.
// redundant for PC superclass but we don't know that yet
// so let LifecycleMetaData determine that
if (_cls.getSuperclass() != null &&
!Object.class.equals(_cls.getSuperclass())) {
recordCallbacks(meta, parseCallbackMethods(_cls.getSuperclass(),
null, true, false, getRepository()), null, true);
}
}
for (FieldMetaData fmd : meta.getDeclaredFields())
if (fmd.getManagement() == FieldMetaData.MANAGE_PERSISTENT)
parseMemberAnnotations(fmd);
// parse fetch groups after fields
if (fgs != null)
parseFetchGroups(meta, fgs);
// always parse mapping after metadata in case there are dependencies
if (isMappingOverrideMode()) {
parseClassMappingAnnotations(meta);
for (FieldMetaData fmd : meta.getDeclaredFields())
if (fmd.getManagement() == FieldMetaData.MANAGE_PERSISTENT)
parseMemberMappingAnnotations(fmd);
}
return meta;
}
/**
* Set the explicit access type, if specified.
*/
private void parseAccess(ClassMetaData meta, Access access) {
if (access != null) {
meta.setAccessType(AccessCode.EXPLICIT
| (access.value() == AccessType.FIELD ?
AccessCode.FIELD : AccessCode.PROPERTY));
}
}
/**
* Parse class mapping annotations.
*/
protected void parseClassMappingAnnotations(ClassMetaData meta) {
}
/**
* Allow subclasses to handle unknown annotations.
*/
protected boolean handleUnknownClassAnnotation(ClassMetaData meta,
Annotation anno) {
return false;
}
/**
* Find or create metadata for the given type. May return null if
* this class has already been parsed fully.
*/
private ClassMetaData getMetaData() {
ClassMetaData meta = getRepository().getCachedMetaData(_cls);
if (meta != null
&& ((isMetaDataMode()
&& (meta.getSourceMode() & MODE_META) != 0)
|| (isMappingMode() && (meta.getSourceMode() & MODE_MAPPING) != 0) ) ) {
if (_log.isWarnEnabled()) {
_log.warn(_loc.get("dup-metadata", _cls.getName()));
}
if(_log.isTraceEnabled()) {
_log.trace(String.format(
"MetaData originally obtained from file: %s under mode :%d with scope %s, and type :%d",
meta.getSourceName(), meta.getSourceMode(), meta.getSourceScope(), meta.getSourceType()));
}
return null;
}
if (meta == null) {
meta = getRepository().addMetaData(_cls, getAccessCode(_cls));
meta.setEnvClassLoader(_envLoader);
meta.setSourceMode(MODE_NONE);
meta.setSource(getSourceFile(), SourceTracker.SRC_ANNOTATIONS, getSourceFile() == null ? ""
: getSourceFile().getPath());
}
return meta;
}
/**
* Gets the explicit access for the class, if any.
* Explicit access type specification does not affect the access type of
* other entity classes or mapped super classes in the entity hierarchy.
*/
private int getAccessCode(Class<?> cls) {
int accessCode = AccessCode.UNKNOWN;
Access access = AccessController.doPrivileged(
J2DoPrivHelper.getAnnotationAction(cls, Access.class));
if (access != null) {
accessCode |= AccessCode.EXPLICIT |
(access.value() == AccessType.FIELD ?
AccessCode.FIELD : AccessCode.PROPERTY);
}
return accessCode;
}
/**
* Determine the source file we're parsing.
*/
protected File getSourceFile() {
if (_file != null)
return _file;
Class<?> cls = _cls;
while (cls.getEnclosingClass() != null)
cls = cls.getEnclosingClass();
String rsrc = StringUtil.replace(cls.getName(), ".", "/");
ClassLoader loader = AccessController.doPrivileged(
J2DoPrivHelper.getClassLoaderAction(cls));
if (loader == null)
loader = AccessController.doPrivileged(
J2DoPrivHelper.getSystemClassLoaderAction());
if (loader == null)
return null;
URL url = AccessController.doPrivileged(
J2DoPrivHelper.getResourceAction(loader, rsrc + ".java"));
if (url == null) {
url = AccessController.doPrivileged(
J2DoPrivHelper.getResourceAction(loader, rsrc + ".class"));
if (url == null)
return null;
}
try {
_file = new File(url.toURI());
} catch (URISyntaxException e) {
} catch (IllegalArgumentException iae) {
// this is thrown when the URI is non-hierarchical (aka JBoss)
}
return _file;
}
/**
* Parse @DataStoreId.
*/
private void parseDataStoreId(ClassMetaData meta, DataStoreId id) {
parseDataStoreId(meta, id.strategy(), id.generator());
}
static void parseDataStoreId(ClassMetaData meta, GenerationType strategy,
String generator) {
meta.setIdentityType(ClassMetaData.ID_DATASTORE);
int strat = getGeneratedValueStrategy(meta, strategy,
generator);
if (strat != -1)
meta.setIdentityStrategy(strat);
else {
switch (strategy) {
case TABLE:
case SEQUENCE:
// technically we should have separate system table and
// sequence generators, but it's easier to just rely on
// the system org.apache.openjpa.Sequence setting for both
if (StringUtil.isEmpty(generator))
meta.setIdentitySequenceName(
SequenceMetaData.NAME_SYSTEM);
else
meta.setIdentitySequenceName(generator);
break;
case AUTO:
meta.setIdentityStrategy(ValueStrategies.NATIVE);
break;
case IDENTITY:
meta.setIdentityStrategy(ValueStrategies.AUTOASSIGN);
break;
default:
throw new UnsupportedException(strategy.toString());
}
}
}
/**
* Warn that @FlushMode is not supported.
*/
private void warnFlushMode(Object context) {
if (_log.isWarnEnabled())
_log.warn(_loc.get("unsupported", "FlushMode", context));
}
/**
* Parse @DataCache.
*
*/
private void parseDataCache(ClassMetaData meta, DataCache cache) {
parseDataCache(meta, cache.enabled(), cache.name(), cache.timeout());
}
static void parseDataCache(ClassMetaData meta,
boolean enabled,
String name,
int timeout) {
if (timeout != Integer.MIN_VALUE) {
meta.setDataCacheTimeout(timeout);
}
String cacheName = name;
if (StringUtil.isEmpty(cacheName)) {
cacheName = org.apache.openjpa.datacache.DataCache.NAME_DEFAULT;
}
meta.setDataCacheName(enabled ? cacheName : null);
}
private void parseManagedInterface(ClassMetaData meta,
ManagedInterface iface) {
meta.setManagedInterface(true);
}
/**
* Parse @DetachedState. The annotation may be null.
*/
private void parseDetachedState(ClassMetaData meta,
DetachedState detached) {
if (detached != null) {
if (!detached.enabled())
meta.setDetachedState(null);
else if (StringUtil.isEmpty(detached.fieldName()))
meta.setDetachedState(ClassMetaData.SYNTHETIC);
else
meta.setDetachedState(detached.fieldName());
} else {
Field[] fields = (Field[]) AccessController.doPrivileged(
J2DoPrivHelper.getDeclaredFieldsAction(
meta.getDescribedType()));
for (Field field : fields)
if (AccessController.doPrivileged(J2DoPrivHelper
.isAnnotationPresentAction(field, DetachedState.class)))
meta.setDetachedState(field.getName());
}
}
/**
* Parse @EntityListeners
*/
private Collection<LifecycleCallbacks>[] parseEntityListeners
(ClassMetaData meta, EntityListeners listeners) {
Class<?>[] classes = listeners.value();
Collection<Class<?>> listenerColl = null;
Collection<LifecycleCallbacks>[] parsed = null;
for (Class<?> cls : classes) {
if (!_conf.getCallbackOptionsInstance().getAllowsDuplicateListener()) {
if (listenerColl == null)
listenerColl = new ArrayList<>();
if (listenerColl.contains(cls))
continue;
listenerColl.add(cls);
}
parsed = parseCallbackMethods(cls, parsed, true, true,
getRepository());
}
return parsed;
}
/**
* Parse callback methods into the given array, and return that array,
* creating one if null. Each index into the array is a collection of
* callback adapters for that numeric event type.
*
* @param sups whether to scan superclasses
* @param listener whether this is a listener or not
*/
public static Collection<LifecycleCallbacks>[] parseCallbackMethods
(Class<?> cls, Collection<LifecycleCallbacks>[] callbacks, boolean sups,
boolean listener, MetaDataRepository repos) {
if (cls == null)
throw new IllegalArgumentException("cls cannot be null");
// first sort / filter based on inheritance
Set<Method> methods = new TreeSet<>(MethodComparator.
getInstance());
int mods;
Class<?> sup = cls;
MethodKey key;
Set<MethodKey> seen = new HashSet<>();
do {
for (Method m : (Method[]) AccessController.doPrivileged(J2DoPrivHelper.getDeclaredMethodsAction(sup))) {
mods = m.getModifiers();
if (Modifier.isStatic(mods) || Modifier.isFinal(mods) ||
Object.class.equals(m.getDeclaringClass()))
continue;
key = new MethodKey(m);
if (!seen.contains(key)) {
methods.add(m);
seen.add(key);
}
}
sup = sup.getSuperclass();
} while (sups && !Object.class.equals(sup));
OpenJPAConfiguration conf = repos.getConfiguration();
for (Method m : methods) {
for (Annotation anno : (Annotation[]) AccessController
.doPrivileged(J2DoPrivHelper
.getDeclaredAnnotationsAction(m))) {
MetaDataTag tag = _tags.get(anno.annotationType());
if (tag == null)
continue;
int[] events = MetaDataParsers.getEventTypes(tag, conf);
if (events == null)
continue;
if (callbacks == null)
callbacks = (Collection<LifecycleCallbacks>[])
new Collection[LifecycleEvent.ALL_EVENTS.length];
for (int e : events) {
if (callbacks[e] == null)
callbacks[e] = new ArrayList<>(3);
MetaDataParsers.validateMethodsForSameCallback(cls,
callbacks[e], m, tag, conf, repos.getLog());
if (listener) {
callbacks[e].add(new BeanLifecycleCallbacks(cls, m,
false));
}
else {
callbacks[e].add(new MethodLifecycleCallbacks(m,
false));
}
}
}
}
return callbacks;
}
/**
* Store lifecycle metadata.
*/
private void recordCallbacks(ClassMetaData cls,
Collection<LifecycleCallbacks>[] callbacks, int[] highs,
boolean superClass) {
if (callbacks == null)
return;
LifecycleMetaData meta = cls.getLifecycleMetaData();
LifecycleCallbacks[] array;
for (int event : LifecycleEvent.ALL_EVENTS) {
if (callbacks[event] == null)
continue;
array = callbacks[event].toArray
(new LifecycleCallbacks[callbacks[event].size()]);
if (superClass) {
meta.setNonPCSuperclassCallbacks(event, array,
(highs == null) ? 0 : highs[event]);
} else {
meta.setDeclaredCallbacks(event, array,
(highs == null) ? 0 : highs[event]);
}
}
}
/**
* Create fetch groups.
* If FetchGroup A includes FetchGroup B, then a bi-link is set between
* A and B. Both A and B must be declared in the same Class.
* <br>
* Call {@link #parseFetchAttribute(ClassMetaData, org.apache.openjpa.meta.FetchGroup, FetchAttributeImpl)}
* only after the
* bi-links have been established, because a field f will not only add the
* fetch group A which explicitly includes f to its custom fetch groups but
* also will also add any fetch group B that includes A.
*/
static void parseFetchGroups(ClassMetaData meta, FetchGroup... groups) {
FetchGroupImpl[] fetchGroupImpls = new FetchGroupImpl[groups.length];
for (int i = 0; i < groups.length; i++) {
FetchAttribute[] fetchAttributes = groups[i].attributes();
FetchAttributeImpl[] fetchAttributeImpls= null;
if (fetchAttributes != null && fetchAttributes.length > 0) {
fetchAttributeImpls = new FetchAttributeImpl[fetchAttributes.length];
for (int j = 0; j < fetchAttributes.length; j++) {
fetchAttributeImpls[j] =
new FetchAttributeImpl(fetchAttributes[j].name(), fetchAttributes[j].recursionDepth());
}
}
fetchGroupImpls[i] = new FetchGroupImpl(groups[i].name(), groups[i].postLoad());
if (fetchAttributeImpls != null) {
fetchGroupImpls[i].setAttributes(fetchAttributeImpls);
}
if (groups[i].fetchGroups() != null) {
fetchGroupImpls[i].setFetchGroups(groups[i].fetchGroups());
}
}
parseFetchGroups(meta, fetchGroupImpls);
}
/**
* Parse fetch group input for the FetchGroup and FetchGroups annotations
* as well as for the fetch-group and fetch-groups XML metadata
* @param meta
* @param groups
*/
static void parseFetchGroups(ClassMetaData meta, FetchGroupImpl... groups) {
org.apache.openjpa.meta.FetchGroup fg;
for (FetchGroupImpl group : groups) {
if (StringUtil.isEmpty(group.name()))
throw new MetaDataException(_loc.get("unnamed-fg", meta));
fg = meta.addDeclaredFetchGroup(group.name());
if (group.postLoad())
fg.setPostLoad(true);
for (String s : group.fetchGroups()) {
fg.addDeclaredInclude(s);
}
}
// Add the parent-child style bi-links between fetch groups in a
// separate pass.
for (FetchGroupImpl group:groups) {
fg = meta.getFetchGroup(group.name());
String[] includedFetchGropNames = fg.getDeclaredIncludes();
for (String includedFectchGroupName:includedFetchGropNames) {
org.apache.openjpa.meta.FetchGroup child =
meta.getFetchGroup(includedFectchGroupName);
if (child == null)
throw new UserException(_loc.get("missing-included-fg",
meta.getDescribedType().getName(), fg.getName(),
includedFectchGroupName));
child.addContainedBy(fg);
}
}
for (FetchGroupImpl group : groups) {
fg = meta.getFetchGroup(group.name());
for (FetchAttributeImpl attr : group.attributes())
parseFetchAttribute(meta, fg, attr);
}
}
/**
* Set a field's fetch group.
*/
private static void parseFetchAttribute(ClassMetaData meta,
org.apache.openjpa.meta.FetchGroup fg, FetchAttributeImpl attr) {
FieldMetaData field = meta.getDeclaredField(attr.name());
if (field == null
|| field.getManagement() != FieldMetaData.MANAGE_PERSISTENT)
throw new MetaDataException(_loc.get("bad-fg-field", fg.getName(),
meta, attr.name()));
field.setInFetchGroup(fg.getName(), true);
Set<String> parentFetchGroups = fg.getContainedBy();
for (Object parentFetchGroup : parentFetchGroups)
field.setInFetchGroup(parentFetchGroup.toString(), true);
if (attr.recursionDepth() != Integer.MIN_VALUE)
fg.setRecursionDepth(field, attr.recursionDepth());
}
/**
* Read annotations for the given member.
*/
private void parseMemberAnnotations(FieldMetaData fmd) {
// look for persistence strategy in annotation table
Member member = getRepository().getMetaDataFactory().getDefaults().
getBackingMember(fmd);
PersistenceStrategy pstrat = PersistenceMetaDataDefaults.
getPersistenceStrategy(fmd, member);
if (pstrat == null)
return;
fmd.setExplicit(true);
AnnotatedElement el = (AnnotatedElement) member;
boolean lob = AccessController.doPrivileged(J2DoPrivHelper
.isAnnotationPresentAction(el, Lob.class));
if (isMetaDataMode()) {
switch (pstrat) {
case BASIC:
parseBasic(fmd, (Basic) el.getAnnotation(Basic.class), lob);
break;
case MANY_ONE:
parseManyToOne(fmd, (ManyToOne) el.getAnnotation
(ManyToOne.class));
break;
case ONE_ONE:
parseOneToOne(fmd, (OneToOne) el.getAnnotation
(OneToOne.class));
break;
case EMBEDDED:
parseEmbedded(fmd, (Embedded) el.getAnnotation
(Embedded.class));
break;
case ONE_MANY:
parseOneToMany(fmd, (OneToMany) el.getAnnotation
(OneToMany.class));
break;
case MANY_MANY:
parseManyToMany(fmd, (ManyToMany) el.getAnnotation
(ManyToMany.class));
break;
case PERS:
parsePersistent(fmd, (Persistent) el.getAnnotation
(Persistent.class));
break;
case PERS_COLL:
parsePersistentCollection(fmd, (PersistentCollection)
el.getAnnotation(PersistentCollection.class));
break;
case ELEM_COLL:
parseElementCollection(fmd, (ElementCollection)
el.getAnnotation(ElementCollection.class));
break;
case PERS_MAP:
parsePersistentMap(fmd, (PersistentMap)
el.getAnnotation(PersistentMap.class));
break;
case TRANSIENT:
break;
default:
throw new InternalException();
}
}
if (isMappingOverrideMode() && lob)
parseLobMapping(fmd);
// extensions
MetaDataTag tag;
for (Annotation anno : el.getDeclaredAnnotations()) {
tag = _tags.get(anno.annotationType());
if (tag == null) {
handleUnknownMemberAnnotation(fmd, anno);
continue;
}
switch (tag) {
case ACCESS:
parseAccess(fmd, (Access)anno);
break;
case FLUSH_MODE:
if (isMetaDataMode())
warnFlushMode(fmd);
break;
case GENERATED_VALUE:
if (isMappingOverrideMode())
parseGeneratedValue(fmd, (GeneratedValue) anno);
break;
case ID:
case EMBEDDED_ID:
fmd.setPrimaryKey(true);
break;
case MAPPED_BY_ID:
parseMapsId(fmd, (MapsId)anno);
break;
case MAP_KEY:
if (isMappingOverrideMode())
parseMapKey(fmd, (MapKey) anno);
break;
case MAP_KEY_CLASS:
if (isMappingOverrideMode())
parseMapKeyClass(fmd, (MapKeyClass) anno);
break;
case ORDER_BY:
parseOrderBy(fmd,
(OrderBy) el.getAnnotation(OrderBy.class));
break;
case SEQ_GENERATOR:
if (isMappingOverrideMode())
parseSequenceGenerator(el, (SequenceGenerator) anno);
break;
case VERSION:
fmd.setVersion(true);
break;
case DEPENDENT:
if (isMetaDataMode() && ((Dependent) anno).value())
fmd.setCascadeDelete(ValueMetaData.CASCADE_AUTO);
break;
case ELEM_DEPENDENT:
if (isMetaDataMode() && ((ElementDependent) anno).value())
fmd.getElement().setCascadeDelete
(ValueMetaData.CASCADE_AUTO);
break;
case ELEM_TYPE:
if (isMetaDataMode())
fmd.getElement().setTypeOverride(toOverrideType
(((ElementType) anno).value()));
break;
case EXTERNAL_VALS:
if (isMetaDataMode())
fmd.setExternalValues(StringUtil.join(((ExternalValues) anno).value(), ","));
break;
case EXTERNALIZER:
if (isMetaDataMode())
fmd.setExternalizer(((Externalizer) anno).value());
break;
case FACTORY:
if (isMetaDataMode())
fmd.setFactory(((Factory) anno).value());
break;
case INVERSE_LOGICAL:
if (isMetaDataMode())
fmd.setInverse(((InverseLogical) anno).value());
break;
case KEY_DEPENDENT:
if (isMetaDataMode() && ((KeyDependent) anno).value())
fmd.getKey()
.setCascadeDelete(ValueMetaData.CASCADE_AUTO);
break;
case KEY_TYPE:
if (isMetaDataMode())
fmd.getKey().setTypeOverride(toOverrideType(((KeyType)
anno).value()));
break;
case LOAD_FETCH_GROUP:
if (isMetaDataMode())
fmd.setLoadFetchGroup(((LoadFetchGroup) anno).value());
break;
case LRS:
if (isMetaDataMode())
fmd.setLRS(((LRS) anno).value());
break;
case READ_ONLY:
if (isMetaDataMode())
parseReadOnly(fmd, (ReadOnly) anno);
break;
case TYPE:
if (isMetaDataMode())
fmd.setTypeOverride(toOverrideType(((Type) anno).
value()));
break;
default:
throw new UnsupportedException(_loc.get("unsupported", fmd,
anno.toString()));
}
}
}
/**
* Parse member mapping components.
*/
protected void parseMemberMappingAnnotations(FieldMetaData fmd) {
}
/**
* Parse @Cache.
*/
private void parseCache(ClassMetaData meta, Cacheable cacheable) {
meta.setCacheEnabled(cacheable.value());
}
/**
* Allow subclasses to handle unknown annotations.
*/
protected boolean handleUnknownMemberAnnotation(FieldMetaData fmd,
Annotation anno) {
return false;
}
/**
* Convert the given class to its OpenJPA type override equivalent.
*/
public static Class<?> toOverrideType(Class<?> cls) {
return (cls == Entity.class)
? org.apache.openjpa.enhance.PersistenceCapable.class : cls;
}
/**
* Parse @ReadOnly.
*/
private void parseReadOnly(FieldMetaData fmd, ReadOnly ro) {
if (ro.value() == UpdateAction.RESTRICT)
fmd.setUpdateStrategy(UpdateStrategies.RESTRICT);
else if (ro.value() == UpdateAction.IGNORE)
fmd.setUpdateStrategy(UpdateStrategies.IGNORE);
else
throw new InternalException();
}
/**
* Sets value generation information for the given field.
*/
private void parseGeneratedValue(FieldMetaData fmd, GeneratedValue gen) {
GenerationType strategy = gen.strategy();
String generator = gen.generator();
parseGeneratedValue(fmd, strategy, generator);
}
/**
* Sets value generation information for the given field.
*/
static void parseGeneratedValue(FieldMetaData fmd, GenerationType strategy,
String generator) {
int strat = getGeneratedValueStrategy(fmd, strategy, generator);
if (strat != -1)
fmd.setValueStrategy(strat);
else {
switch (strategy) {
case TABLE:
case SEQUENCE:
// technically we should have separate system table and
// sequence generators, but it's easier to just rely on
// the system org.apache.openjpa.Sequence setting for both
if (StringUtil.isEmpty(generator))
fmd.setValueSequenceName(SequenceMetaData.NAME_SYSTEM);
else
fmd.setValueSequenceName(generator);
break;
case AUTO:
fmd.setValueSequenceName(SequenceMetaData.NAME_SYSTEM);
break;
case IDENTITY:
fmd.setValueStrategy(ValueStrategies.AUTOASSIGN);
break;
default:
throw new UnsupportedException(strategy.toString());
}
}
}
/**
* Return the value strategy for the given generator, or -1 if the
* strategy depends on the <code>GenerationType</code> rather than the
* generator name.
*/
private static int getGeneratedValueStrategy(Object context,
GenerationType strategy, String generator) {
if (strategy != AUTO || StringUtil.isEmpty(generator))
return -1;
if (Generator.UUID_HEX.equals(generator))
return ValueStrategies.UUID_HEX;
if (Generator.UUID_STRING.equals(generator))
return ValueStrategies.UUID_STRING;
if (Generator.UUID_TYPE4_HEX.equals(generator))
return ValueStrategies.UUID_TYPE4_HEX;
if (Generator.UUID_TYPE4_STRING.equals(generator))
return ValueStrategies.UUID_TYPE4_STRING;
throw new MetaDataException(_loc.get("generator-bad-strategy",
context, generator));
}
/**
* Parse @Basic. Given annotation may be null.
*/
private void parseBasic(FieldMetaData fmd, Basic anno, boolean lob) {
Class<?> type = fmd.getDeclaredType();
if (lob && type != String.class
&& type != char[].class && type != Character[].class
&& type != byte[].class && type != Byte[].class)
fmd.setSerialized(true);
else if (!lob) {
switch (fmd.getDeclaredTypeCode()) {
case JavaTypes.OBJECT:
if (Enum.class.isAssignableFrom(type))
break;
// else no break
case JavaTypes.COLLECTION:
case JavaTypes.MAP:
case JavaTypes.PC:
case JavaTypes.PC_UNTYPED:
if (Serializable.class.isAssignableFrom(type))
fmd.setSerialized(true);
else
throw new MetaDataException(_loc.get("bad-meta-anno",
fmd, "Basic"));
break;
case JavaTypes.ARRAY:
if (type == char[].class || type == Character[].class
|| type == byte[].class || type == Byte[].class)
break;
if (Serializable.class.isAssignableFrom
(type.getComponentType()))
fmd.setSerialized(true);
else
throw new MetaDataException(_loc.get("bad-meta-anno",
fmd, "Basic"));
break;
}
}
if (anno == null)
return;
fmd.setInDefaultFetchGroup(anno.fetch() == FetchType.EAGER);
if (!anno.optional())
fmd.setNullValue(FieldMetaData.NULL_EXCEPTION);
}
/**
* Parse @ManyToOne.
*/
private void parseManyToOne(FieldMetaData fmd, ManyToOne anno) {
if (!JavaTypes.maybePC(fmd.getValue()))
throw new MetaDataException(_loc.get("bad-meta-anno", fmd,
"ManyToOne"));
// don't specifically exclude relation from DFG b/c that will prevent
// us from even reading the fk when reading from the primary table,
// which is not what most users will want
if (anno.fetch() == FetchType.EAGER)
fmd.setInDefaultFetchGroup(true);
if (!anno.optional())
fmd.setNullValue(FieldMetaData.NULL_EXCEPTION);
if (anno.targetEntity() != void.class)
fmd.setTypeOverride(anno.targetEntity());
setCascades(fmd, anno.cascade());
fmd.setAssociationType(FieldMetaData.MANY_TO_ONE);
}
/**
* Parse @OneToOne.
*/
private void parseOneToOne(FieldMetaData fmd, OneToOne anno) {
if (!JavaTypes.maybePC(fmd.getValue()))
throw new MetaDataException(_loc.get("bad-meta-anno", fmd,
"OneToOne"));
// don't specifically exclude relation from DFG b/c that will prevent
// us from even reading the fk when reading from the primary table,
// which is not what most users will want
if (anno.fetch() == FetchType.EAGER)
fmd.setInDefaultFetchGroup(true);
if (!anno.optional())
fmd.setNullValue(FieldMetaData.NULL_EXCEPTION);
if (isMappingOverrideMode() && !StringUtil.isEmpty(anno.mappedBy()))
fmd.setMappedBy(anno.mappedBy());
if (anno.targetEntity() != void.class)
fmd.setTypeOverride(anno.targetEntity());
setCascades(fmd, anno.cascade());
setOrphanRemoval(fmd, anno.orphanRemoval());
fmd.setAssociationType(FieldMetaData.ONE_TO_ONE);
}
/**
* Parse @Embedded. Given annotation may be null.
*/
private void parseEmbedded(FieldMetaData fmd, Embedded anno) {
if (!JavaTypes.maybePC(fmd.getValue()))
throw new MetaDataException(_loc.get("bad-meta-anno", fmd,
"Embedded"));
fmd.setInDefaultFetchGroup(true);
fmd.setEmbedded(true);
if (fmd.getEmbeddedMetaData() == null)
fmd.addEmbeddedMetaData(getAccessCode(fmd.getDeclaredType()));
}
/**
* Parse @OneToMany.
*/
private void parseOneToMany(FieldMetaData fmd, OneToMany anno) {
switch (fmd.getDeclaredTypeCode()) {
case JavaTypes.ARRAY:
case JavaTypes.COLLECTION:
case JavaTypes.MAP:
if (JavaTypes.maybePC(fmd.getElement()))
break;
// no break
default:
throw new MetaDataException(_loc.get("bad-meta-anno", fmd,
"OneToMany"));
}
fmd.setInDefaultFetchGroup(anno.fetch() == FetchType.EAGER);
if (isMappingOverrideMode() && !StringUtil.isEmpty(anno.mappedBy()))
fmd.setMappedBy(anno.mappedBy());
if (anno.targetEntity() != void.class)
fmd.getElement().setDeclaredType(anno.targetEntity());
setCascades(fmd.getElement(), anno.cascade());
setOrphanRemoval(fmd.getElement(), anno.orphanRemoval());
fmd.setAssociationType(FieldMetaData.ONE_TO_MANY);
}
/**
* Parse @ManyToMany.
*/
private void parseManyToMany(FieldMetaData fmd, ManyToMany anno) {
switch (fmd.getDeclaredTypeCode()) {
case JavaTypes.ARRAY:
case JavaTypes.COLLECTION:
case JavaTypes.MAP:
if (fmd.getDeclaredType() == Properties.class || JavaTypes.maybePC(fmd.getElement()))
break;
// no break
default:
throw new MetaDataException(_loc.get("bad-meta-anno", fmd,
"ManyToMany"));
}
fmd.setInDefaultFetchGroup(anno.fetch() == FetchType.EAGER);
if (isMappingOverrideMode() && !StringUtil.isEmpty(anno.mappedBy()))
fmd.setMappedBy(anno.mappedBy());
if (anno.targetEntity() != void.class)
fmd.getElement().setDeclaredType(anno.targetEntity());
setCascades(fmd.getElement(), anno.cascade());
fmd.setAssociationType(FieldMetaData.MANY_TO_MANY);
}
/**
* Parse @MapKey.
*/
private void parseMapKey(FieldMetaData fmd, MapKey anno) {
String name = anno.name();
if (StringUtil.isEmpty(name))
fmd.getKey().setValueMappedBy(ValueMetaData.MAPPED_BY_PK);
else
fmd.getKey().setValueMappedBy(name);
}
/**
* Parse @MapKeyClass.
*/
private void parseMapKeyClass(FieldMetaData fmd, MapKeyClass anno) {
if (anno.value() != void.class)
fmd.getKey().setDeclaredType(anno.value());
else
throw new IllegalArgumentException(
"The value of the MapClassClass cannot be null");
}
/**
* Parse @MapsId.
*/
private void parseMapsId(FieldMetaData fmd, MapsId anno) {
String value = anno.value();
if (value != null)
fmd.setMappedByIdValue(value);
else
fmd.setMappedByIdValue("");
}
/**
* Setup the field as a LOB mapping.
*/
protected void parseLobMapping(FieldMetaData fmd) {
}
/**
* Parse @OrderBy.
*/
private void parseOrderBy(FieldMetaData fmd, OrderBy anno) {
String dec = anno.value();
if (fmd.isElementCollection() &&
fmd.getElement().getEmbeddedMetaData() != null) {
if (dec.length() == 0 || dec.equals("ASC") ||
dec.equals("DESC"))
throw new MetaDataException(_loc.get(
"invalid-orderBy", fmd));
}
if (dec.length() == 0 || dec.equals("ASC"))
dec = Order.ELEMENT + " asc";
else if (dec.equals("DESC"))
dec = Order.ELEMENT + " desc";
fmd.setOrderDeclaration(dec);
}
/**
* Parse @Persistent.
*/
private void parsePersistent(FieldMetaData fmd, Persistent anno) {
switch (fmd.getDeclaredTypeCode()) {
case JavaTypes.ARRAY:
if (fmd.getDeclaredType() == byte[].class
|| fmd.getDeclaredType() == Byte[].class
|| fmd.getDeclaredType() == char[].class
|| fmd.getDeclaredType() == Character[].class)
break;
// no break
case JavaTypes.COLLECTION:
case JavaTypes.MAP:
throw new MetaDataException(_loc.get("bad-meta-anno", fmd,
"Persistent"));
}
if (!StringUtil.isEmpty(anno.mappedBy()))
fmd.setMappedBy(anno.mappedBy());
fmd.setInDefaultFetchGroup(anno.fetch() == FetchType.EAGER);
if (!anno.optional())
fmd.setNullValue(FieldMetaData.NULL_EXCEPTION);
setCascades(fmd, anno.cascade());
if (anno.embedded()) {
if (!JavaTypes.maybePC(fmd.getValue()))
throw new MetaDataException(_loc.get("bad-meta-anno", fmd,
"Persistent(embedded=true)"));
fmd.setEmbedded(true);
if (fmd.getEmbeddedMetaData() == null) {
fmd.addEmbeddedMetaData(getAccessCode(fmd.getDeclaredType()));
}
}
}
/**
* Parse @PersistentCollection.
*/
private void parsePersistentCollection(FieldMetaData fmd,
PersistentCollection anno) {
if (fmd.getDeclaredTypeCode() != JavaTypes.ARRAY
&& fmd.getDeclaredTypeCode() != JavaTypes.COLLECTION)
throw new MetaDataException(_loc.get("bad-meta-anno", fmd,
"PersistentCollection"));
fmd.setPersistentCollection(true);
if (!StringUtil.isEmpty(anno.mappedBy()))
fmd.setMappedBy(anno.mappedBy());
fmd.setInDefaultFetchGroup(anno.fetch() == FetchType.EAGER);
if (anno.elementType() != void.class)
fmd.getElement().setDeclaredType(anno.elementType());
setCascades(fmd.getElement(), anno.elementCascade());
if (anno.elementEmbedded()) {
if (!JavaTypes.maybePC(fmd.getElement()))
throw new MetaDataException(_loc.get("bad-meta-anno", fmd,
"PersistentCollection(embeddedElement=true)"));
fmd.getElement().setEmbedded(true);
if (fmd.getElement().getEmbeddedMetaData() == null) {
fmd.getElement().addEmbeddedMetaData(
getAccessCode(fmd.getElement().getDeclaredType()));
}
}
}
/**
* Parse @ElementCollection.
*/
private void parseElementCollection(FieldMetaData fmd,
ElementCollection anno) {
// TODO: throw exception if the runtime env is OpenJpa 1.x
if (fmd.getDeclaredTypeCode() != JavaTypes.COLLECTION &&
fmd.getDeclaredTypeCode() != JavaTypes.MAP)
throw new MetaDataException(_loc.get("bad-meta-anno", fmd,
"ElementCollection"));
if (anno.targetClass() != void.class)
fmd.getElement().setDeclaredType(anno.targetClass());
fmd.setInDefaultFetchGroup(anno.fetch() == FetchType.EAGER);
fmd.setElementCollection(true);
ValueMetaData elem = fmd.getElement();
boolean isEnum = elem.getDeclaredType().isEnum();
if (!isEnum && JavaTypes.maybePC(elem)) {
elem.setEmbedded(true);
if (elem.getEmbeddedMetaData() == null)
elem.addEmbeddedMetaData(getAccessCode(elem.getDeclaredType()));
}
}
/**
* Parse @PersistentMap.
*/
private void parsePersistentMap(FieldMetaData fmd, PersistentMap anno) {
if (fmd.getDeclaredTypeCode() != JavaTypes.MAP)
throw new MetaDataException(_loc.get("bad-meta-anno", fmd,
"PersistentMap"));
fmd.setInDefaultFetchGroup(anno.fetch() == FetchType.EAGER);
if (anno.keyType() != void.class)
fmd.getKey().setDeclaredType(anno.keyType());
if (anno.elementType() != void.class)
fmd.getElement().setDeclaredType(anno.elementType());
setCascades(fmd.getKey(), anno.keyCascade());
setCascades(fmd.getElement(), anno.elementCascade());
if (anno.keyEmbedded()) {
if (!JavaTypes.maybePC(fmd.getKey()))
throw new MetaDataException(_loc.get("bad-meta-anno", fmd,
"PersistentMap(embeddedKey=true)"));
fmd.getKey().setEmbedded(true);
if (fmd.getKey().getEmbeddedMetaData() == null) {
fmd.getKey().addEmbeddedMetaData(
getAccessCode(fmd.getKey().getDeclaredType()));
}
}
if (anno.elementEmbedded()) {
if (!JavaTypes.maybePC(fmd.getElement()))
throw new MetaDataException(_loc.get("bad-meta-anno", fmd,
"PersistentMap(embeddedValue=true)"));
fmd.getElement().setEmbedded(true);
if (fmd.getElement().getEmbeddedMetaData() == null)
fmd.getElement().addEmbeddedMetaData(
getAccessCode(fmd.getElement().getDeclaredType()));
}
}
/**
* Set cascades on relation.
*/
private void setCascades(ValueMetaData vmd, CascadeType[] cascades) {
for (CascadeType cascade : cascades) {
if (cascade == CascadeType.ALL || cascade == CascadeType.REMOVE)
vmd.setCascadeDelete(ValueMetaData.CASCADE_IMMEDIATE);
if (cascade == CascadeType.ALL || cascade == CascadeType.PERSIST)
vmd.setCascadePersist(ValueMetaData.CASCADE_IMMEDIATE, false);
if (cascade == CascadeType.ALL || cascade == CascadeType.MERGE)
vmd.setCascadeAttach(ValueMetaData.CASCADE_IMMEDIATE);
if (cascade == CascadeType.ALL || cascade == CascadeType.DETACH)
vmd.setCascadeDetach(ValueMetaData.CASCADE_IMMEDIATE);
if (cascade == CascadeType.ALL || cascade == CascadeType.REFRESH)
vmd.setCascadeRefresh(ValueMetaData.CASCADE_IMMEDIATE);
}
}
private void setOrphanRemoval(ValueMetaData vmd, boolean orphanRemoval) {
if (orphanRemoval)
vmd.setCascadeDelete(ValueMetaData.CASCADE_AUTO);
}
/**
* Parse @SequenceGenerator.
*/
private void parseSequenceGenerator(AnnotatedElement el,
SequenceGenerator gen) {
String name = gen.name();
if (StringUtil.isEmpty(name))
throw new MetaDataException(_loc.get("no-seq-name", el));
if (_log.isTraceEnabled())
_log.trace(_loc.get("parse-sequence", name));
SequenceMetaData meta = getRepository().getCachedSequenceMetaData
(name);
if (meta != null) {
if (_log.isWarnEnabled())
_log.warn(_loc.get("dup-sequence", name, el));
return;
}
// create new sequence
meta = getRepository().addSequenceMetaData(name);
String seq = gen.sequenceName();
// Do not normalize the sequence name if it appears to be a plugin
if (seq.indexOf('(') == -1){
seq = normalizeSequenceName(seq);
}
int initial = gen.initialValue();
int allocate = gen.allocationSize();
String schema = normalizeSchemaName(gen.schema());
String catalog = normalizeCatalogName(gen.catalog());
// don't allow initial of 0 b/c looks like def value
if (initial == 0)
initial = 1;
// create plugin string from info
String clsName, props;
if (StringUtil.isEmpty(seq)) {
clsName = SequenceMetaData.IMPL_NATIVE;
props = null;
} else if (seq.indexOf('(') != -1) // plugin
{
clsName = Configurations.getClassName(seq);
props = Configurations.getProperties(seq);
seq = null;
} else {
clsName = SequenceMetaData.IMPL_NATIVE;
props = null;
}
meta.setSequencePlugin(Configurations.getPlugin(clsName, props));
meta.setSequence(seq);
meta.setInitialValue(initial);
meta.setAllocate(allocate);
meta.setSchema(schema);
meta.setCatalog(catalog);
meta.setSource(getSourceFile(), (el instanceof Class) ? el : null,
SourceTracker.SRC_ANNOTATIONS);
}
/**
* Parse @NamedQuery.
*/
private void parseNamedQueries(AnnotatedElement el, NamedQuery... queries) {
QueryMetaData meta;
for (NamedQuery query : queries) {
if (StringUtil.isEmpty(query.name()))
throw new MetaDataException(_loc.get("no-query-name", el));
if (StringUtil.isEmpty(query.query()))
throw new MetaDataException(_loc.get("no-query-string",
query.name(), el));
if (_log.isTraceEnabled())
_log.trace(_loc.get("parse-query", query.name()));
meta = getRepository().searchQueryMetaDataByName(query.name());
if (meta != null) {
Class<?> definingType = meta.getDefiningType();
if ((definingType == null || definingType != _cls)
&& _log.isWarnEnabled()) {
_log.warn(_loc.get("dup-query", query.name(), el,
definingType));
}
continue;
}
meta = getRepository().addQueryMetaData(_cls, query.name());
meta.setLanguage(JPQLParser.LANG_JPQL);
meta.setQueryString(query.query());
for (QueryHint hint : query.hints())
meta.addHint(hint.name(), hint.value());
LockModeType lmt = processNamedQueryLockModeType(query);
if (lmt != null && lmt != LockModeType.NONE) {
meta.addHint("openjpa.FetchPlan.ReadLockMode", lmt);
}
meta.setSource(getSourceFile(), (el instanceof Class) ? el : null,
SourceTracker.SRC_ANNOTATIONS, getSourceFile() == null ? "" : getSourceFile().getPath());
if (isMetaDataMode())
meta.setSourceMode(MODE_META);
else if (isMappingMode())
meta.setSourceMode(MODE_MAPPING);
else
meta.setSourceMode(MODE_QUERY);
}
}
/**
* A private worker method that calculates the lock mode for an individual NamedQuery. If the NamedQuery is
* configured to use the NONE lock mode(explicit or implicit), this method will promote the lock to a READ
* level lock. This was done to allow for JPA1 apps to function properly under a 2.0 runtime.
*/
private LockModeType processNamedQueryLockModeType(NamedQuery query) {
LockModeType lmt = query.lockMode();
if (query.lockMode() != null) {
String lm = _conf.getLockManager();
boolean optimistic = _conf.getOptimistic();
if (lm != null) {
lm = lm.toLowerCase();
if (lm.contains("pessimistic")) {
if (lmt == LockModeType.NONE && !optimistic) {
if (_log.isWarnEnabled()) {
_log.warn(_loc.get("override-named-query-lock-mode", new String[] { "annotation",
query.name(), _cls.getName() }));
}
lmt = LockModeType.READ;
}
}
}
}
return lmt;
}
/**
* Parse @NamedNativeQuery.
*/
private void parseNamedNativeQueries(AnnotatedElement el,
NamedNativeQuery... queries) {
QueryMetaData meta;
for (NamedNativeQuery query : queries) {
if (StringUtil.isEmpty(query.name()))
throw new MetaDataException(_loc.get("no-native-query-name",
el));
if (StringUtil.isEmpty(query.query()))
throw new MetaDataException(_loc.get("no-native-query-string",
query.name(), el));
if (_log.isTraceEnabled())
_log.trace(_loc.get("parse-native-query", query.name()));
meta = getRepository().searchQueryMetaDataByName(query.name());
if (meta != null) {
Class<?> defType = meta.getDefiningType();
if ((defType != _cls) && _log.isWarnEnabled()) {
_log.warn(_loc.get("dup-query", query.name(), el, defType));
}
continue;
}
meta = getRepository().addQueryMetaData(null, query.name());
meta.setLanguage(QueryLanguages.LANG_SQL);
meta.setQueryString(query.query());
Class<?> res = query.resultClass();
if (ImplHelper.isManagedType(getConfiguration(), res))
meta.setCandidateType(res);
else if (!void.class.equals(res))
meta.setResultType(res);
if (!StringUtil.isEmpty(query.resultSetMapping()))
meta.setResultSetMappingName(query.resultSetMapping());
for (QueryHint hint : query.hints())
meta.addHint(hint.name(), hint.value());
meta.setSource(getSourceFile(), (el instanceof Class) ? el : null,
SourceTracker.SRC_ANNOTATIONS, getSourceFile() == null ? "" : getSourceFile().getPath());
if (isMetaDataMode())
meta.setSourceMode(MODE_META);
else if (isMappingMode())
meta.setSourceMode(MODE_MAPPING);
else
meta.setSourceMode(MODE_QUERY);
}
}
/**
* Set the explicit access type, if specified.
*/
private void parseAccess(FieldMetaData meta, Access access) {
if (access != null) {
meta.setAccessType(AccessCode.EXPLICIT
| (access.value() == AccessType.FIELD ?
AccessCode.FIELD : AccessCode.PROPERTY));
}
}
private static class MethodKey {
private final Method _method;
public MethodKey(Method m) {
_method = m;
}
@Override
public int hashCode() {
int code = 46 * 12 + _method.getName().hashCode();
for (Class<?> param : _method.getParameterTypes())
code = 46 * code + param.hashCode();
return code;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof MethodKey))
return false;
Method other = ((MethodKey) o)._method;
if (!_method.getName().equals(other.getName()))
return false;
return Arrays.equals(_method.getParameterTypes(),
other.getParameterTypes());
}
}
private static class MethodComparator implements Comparator<Method> {
private static MethodComparator INSTANCE = null;
public static MethodComparator getInstance() {
if (INSTANCE == null)
INSTANCE = new MethodComparator();
return INSTANCE;
}
@Override
public int compare(Method m1, Method m2) {
Class<?> c1 = m1.getDeclaringClass();
Class<?> c2 = m2.getDeclaringClass();
if (!c1.equals(c2)) {
if (c1.isAssignableFrom(c2))
return -1;
else
return 1;
}
int compare = m1.getName().compareTo(m2.getName ());
if (compare != 0) {
return compare;
}
Class<?>[] params1 = m1.getParameterTypes();
Class<?>[] params2 = m2.getParameterTypes();
compare = params1.length - params2.length;
if (compare != 0) {
return compare;
}
// Just using the Method#hashCode() is not enough as it only contains
// the hash of the class + the hash of the NAME of the method...
// Thus if they have the same number of parameters, we need to compare them all
for (int i = 0; i < params1.length; i++) {
compare = params1[i].hashCode() - params2[i].hashCode();
if (compare != 0) {
return compare;
}
}
return 0;
}
}
/**
* An internal class used to mimic the FetchGroup annotation.
* This is needed to process the fetch-group element in xml
* metadata with common code for the annotation.
*/
static class FetchGroupImpl {
private String name = "";
private boolean postLoad = false;
private FetchAttributeImpl[] attributes = {};
private String[] fetchGroups = {};
FetchGroupImpl(String name, boolean postLoad)
{
this.name = name;
this.postLoad = postLoad;
}
public String name() {
return name;
}
public boolean postLoad() {
return postLoad;
}
public FetchAttributeImpl[] attributes() {
return attributes;
}
public String[] fetchGroups() {
return fetchGroups;
}
public void setAttributes(FetchAttributeImpl[] attributes) {
this.attributes = attributes;
}
public void setFetchGroups(String[] fetchGroups) {
this.fetchGroups = fetchGroups;
}
}
/**
* An internal class used to mimic the FetchAttribute annotation.
* This is needed to process the fetch-attribute element in xml
* metadata with common code for the annotation.
*/
static class FetchAttributeImpl {
private String name = "";
private int recursionDepth = Integer.MIN_VALUE;
public FetchAttributeImpl(String name, int recursionDepth) {
this.name = name;
this.recursionDepth = recursionDepth;
}
public String name() {
return name;
}
public int recursionDepth() {
return recursionDepth;
}
}
protected String normalizeSequenceName(String seqName) {
return seqName;
}
protected String normalizeSchemaName(String schName) {
return schName;
}
protected String normalizeCatalogName(String catName) {
return catName;
}
protected MultiQueryMetaData.Parameter.Mode toKernelParameterMode(ParameterMode mode) {
switch (mode) {
case IN : return MultiQueryMetaData.Parameter.Mode.IN;
case OUT: return MultiQueryMetaData.Parameter.Mode.OUT;
case INOUT: return MultiQueryMetaData.Parameter.Mode.INOUT;
case REF_CURSOR: return MultiQueryMetaData.Parameter.Mode.CURSOR;
default : return MultiQueryMetaData.Parameter.Mode.IN;
}
}
protected void addSourceInfo(AnnotatedElement el, QueryMetaData meta) {
meta.setSource(getSourceFile(), (el instanceof Class) ? el : null,
SourceTracker.SRC_ANNOTATIONS, getSourceFile() == null ? "" : getSourceFile().getPath());
if (isMetaDataMode())
meta.setSourceMode(MODE_META);
else if (isMappingMode())
meta.setSourceMode(MODE_MAPPING);
else
meta.setSourceMode(MODE_QUERY);
}
protected void addHints(QueryMetaData meta, QueryHint...hints) {
for (QueryHint hint : hints)
meta.addHint(hint.name(), hint.value());
}
private void parseNamedStoredProcedureQueries(AnnotatedElement el, NamedStoredProcedureQuery... procs) {
for (NamedStoredProcedureQuery proc : procs) {
if (StringUtil.isEmpty(proc.name()))
throw new MetaDataException(_loc.get("stored-proc-no-name", el));
if (StringUtil.isEmpty(proc.procedureName()))
throw new MetaDataException(_loc.get("stored-proc-no-dbname", el));
// Query metadata name
MultiQueryMetaData meta = new MultiQueryMetaData(_cls, proc.name(), proc.procedureName(), false);
QueryMetaData existing = getRepository().addQueryMetaData(meta);
if (existing != null && existing.getDefiningType() != meta.getDefiningType()) {
getLog().warn(_loc.get("dup-query", meta.getName(), el, existing.getDefiningType()));
}
// Important: The query string is the name of the database stored procedure
meta.setQueryString(proc.procedureName());
// For each mapping name/result class, add a component metadata
// The spec restricts that either ResultMappingName or ResultClasses be specified, but not both.
// This is relevant because the order of mapping must match the order in which the
// components are returned
Class<?>[] resultClasses = proc.resultClasses();
String[] resultSetMappings = proc.resultSetMappings();
if (resultClasses.length > 0 && resultSetMappings.length > 0)
throw new MetaDataException(_loc.get("stored-proc-both-mapping", el));
for (Class<?> res : resultClasses) {
meta.addComponent(res);
}
for (String mapping : resultSetMappings) {
meta.addComponent(mapping);
}
StoredProcedureParameter[] params = proc.parameters();
for (StoredProcedureParameter param : params) {
MultiQueryMetaData.Parameter p = new MultiQueryMetaData.Parameter(
param.name(), param.type(), toKernelParameterMode(param.mode()));
meta.registerParameter(p);
}
addHints(meta, proc.hints());
addSourceInfo(el, meta);
}
}
}