/* * 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.lib.util.StringUtil; 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.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<Class<?>, MetaDataTag>(); 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<Package, Integer>(); // the class we were invoked to parse protected Class<?> _cls = null; protected Stack<Class<?>> _stack = new Stack<Class<?>>(); 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.intValue() | 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))) .booleanValue() && !(AccessController.doPrivileged(J2DoPrivHelper.isAnnotationPresentAction(_cls, Embeddable.class))) .booleanValue() && !(AccessController.doPrivileged(J2DoPrivHelper.isAnnotationPresentAction(_cls, MappedSuperclass.class))).booleanValue()) 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 (int i = 0; i < fields.length; i++) if ((AccessController.doPrivileged(J2DoPrivHelper .isAnnotationPresentAction(fields[i], DetachedState.class))) .booleanValue()) meta.setDetachedState(fields[i].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<Class<?>>(); 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<Method>(MethodComparator. getInstance()); int mods; Class<?> sup = cls; MethodKey key; Set<MethodKey> seen = new HashSet<MethodKey>(); 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 i = 0; i < events.length; i++) { int e = events[i]; if (callbacks[e] == null) callbacks[e] = new ArrayList<LifecycleCallbacks>(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))).booleanValue(); 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() == true) { _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; } public int hashCode() { int code = 46 * 12 + _method.getName().hashCode(); for (Class<?> param : _method.getParameterTypes()) code = 46 * code + param.hashCode(); return code; } 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; } 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); } } }