org.apache.openjpa.meta.MetaDataRepository Maven / Gradle / Ivy
The newest version!
/*
* 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.meta;
import java.io.Serializable;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.enhance.DynamicPersistenceCapable;
import org.apache.openjpa.enhance.PCEnhancer;
import org.apache.openjpa.enhance.PCRegistry;
import org.apache.openjpa.enhance.PCRegistry.RegisterClassListener;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.event.LifecycleEventManager;
import org.apache.openjpa.lib.conf.Configurable;
import org.apache.openjpa.lib.conf.Configuration;
import org.apache.openjpa.lib.conf.Configurations;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.ClassUtil;
import org.apache.openjpa.lib.util.Closeable;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.MultiClassLoader;
import org.apache.openjpa.lib.util.Options;
import org.apache.openjpa.lib.util.StringDistance;
import org.apache.openjpa.util.ClassResolver;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.MetaDataException;
import org.apache.openjpa.util.OpenJPAId;
/**
* Repository of and factory for persistent metadata.
*
* @since 0.3.0
* @author Abe White
* @author Steve Kim (query metadata)
*/
public class MetaDataRepository implements PCRegistry.RegisterClassListener, Configurable, Closeable, MetaDataModes,
Serializable {
private static final long serialVersionUID = 1L;
/**
* Constant to not validate any metadata.
*/
public static final int VALIDATE_NONE = 0;
/**
* Bit flag to validate metadata.
*/
public static final int VALIDATE_META = 1;
/**
* Bit flag to validate mappings.
*/
public static final int VALIDATE_MAPPING = 2;
/**
* Bit flag to validate unenhanced metadata only.
*/
public static final int VALIDATE_UNENHANCED = 4;
/**
* Bit flag for runtime validation. Requires that all classes are enhanced, and performs extra
* field resolution steps.
*/
public static final int VALIDATE_RUNTIME = 8;
protected static final Class>[] EMPTY_CLASSES = new Class[0];
protected static final NonPersistentMetaData[] EMPTY_NON_PERSISTENT = new NonPersistentMetaData[0];
protected final ClassMetaData[] EMPTY_METAS;
protected final FieldMetaData[] EMPTY_FIELDS;
protected final Order[] EMPTY_ORDERS;
private static final Localizer _loc = Localizer.forPackage(MetaDataRepository.class);
// system sequence
private SequenceMetaData _sysSeq = null;
// cache of parsed metadata, oid class to class, and interface class
// to metadatas
private Map, ClassMetaData> _metas = new HashMap<>();
private Map _metaStringMap = new ConcurrentHashMap<>();
private Map, Class>> _oids = Collections.synchronizedMap(new HashMap<>());
private Map, Collection>> _impls =
Collections.synchronizedMap(new HashMap<>());
private Map, Class>> _ifaces = Collections.synchronizedMap(new HashMap<>());
private Map _queries = new HashMap<>();
private Map _seqs = new HashMap<>();
private Map>> _aliases = Collections.synchronizedMap(new HashMap<>());
private Map, NonPersistentMetaData> _pawares =
Collections.synchronizedMap(new HashMap<>());
private Map, NonPersistentMetaData> _nonMapped =
Collections.synchronizedMap(new HashMap<>());
private Map, Class>> _metamodel = Collections.synchronizedMap(new HashMap<>());
// map of classes to lists of their subclasses
private Map, Collection>> _subs =
Collections.synchronizedMap(new HashMap<>());
// xml mapping
protected final XMLMetaData[] EMPTY_XMLMETAS;
private final Map, XMLMetaData> _xmlmetas = new HashMap<>();
private transient OpenJPAConfiguration _conf = null;
private transient Log _log = null;
private transient InterfaceImplGenerator _implGen = null;
private transient MetaDataFactory _factory = null;
private int _resMode = MODE_META | MODE_MAPPING;
private int _sourceMode = MODE_META | MODE_MAPPING | MODE_QUERY;
private int _validate = VALIDATE_META | VALIDATE_UNENHANCED;
// we buffer up any classes that register themselves to prevent
// reentrancy errors if classes register during a current parse (common)
private final Collection> _registered = new HashSet<>();
// set of metadatas we're in the process of resolving
private final List _resolving = new ArrayList<>();
private final List _mapping = new ArrayList<>();
private final List _errs = new LinkedList<>();
// system listeners
private LifecycleEventManager.ListenerList _listeners = new LifecycleEventManager.ListenerList(3);
private boolean _systemListenersActivated = false;
protected boolean _preload = false;
protected boolean _preloadComplete = false;
protected boolean _locking = true;
private static final String PRELOAD_STR = "Preload";
// A boolean used to decide whether or not we need to call to PCEnhancer to check whether we have any down level
// Entities.
private boolean _logEnhancementLevel = true;
// A boolean used to decide whether to filter Class> objects submitted by the PCRegistry listener system
private boolean _filterRegisteredClasses = false;
// we should skip these types for the enhancement
private Collection> _typesWithoutEnhancement;
/**
* Default constructor. Configure via {@link Configurable}.
*/
public MetaDataRepository() {
EMPTY_METAS = newClassMetaDataArray(0);
EMPTY_FIELDS = newFieldMetaDataArray(0);
EMPTY_ORDERS = newOrderArray(0);
EMPTY_XMLMETAS = newXMLClassMetaDataArray(0);
}
/**
* Return the configuration for the repository.
*/
public OpenJPAConfiguration getConfiguration() {
return _conf;
}
/**
* Return the metadata log.
*/
public Log getLog() {
return _log;
}
/**
* The I/O used to load metadata.
*/
public MetaDataFactory getMetaDataFactory() {
return _factory;
}
/**
* The I/O used to load metadata.
*/
public void setMetaDataFactory(MetaDataFactory factory) {
factory.setRepository(this);
_factory = factory;
}
/**
* The metadata validation level. Defaults to VALIDATE_META | VALIDATE_UNENHANCED
.
*/
public int getValidate() {
return _validate;
}
/**
* The metadata validation level. Defaults to VALIDATE_META | VALIDATE_UNENHANCED
.
*/
public void setValidate(int validate) {
_validate = validate;
}
/**
* The metadata validation level. Defaults to
* VALIDATE_META | VALIDATE_MAPPING | VALIDATE_UNENHANCED
.
*/
public void setValidate(int validate, boolean on) {
if (validate == VALIDATE_NONE)
_validate = validate;
else if (on)
_validate |= validate;
else
_validate &= ~validate;
}
/**
* The metadata resolution mode. Defaults to MODE_META | MODE_MAPPING
.
*/
public int getResolve() {
return _resMode;
}
/**
* The metadata resolution mode. Defaults to MODE_META | MODE_MAPPING
.
*/
public void setResolve(int mode) {
_resMode = mode;
}
/**
* The metadata resolution mode. Defaults to MODE_META | MODE_MAPPING
.
*/
public void setResolve(int mode, boolean on) {
if (mode == MODE_NONE)
_resMode = mode;
else if (on)
_resMode |= mode;
else
_resMode &= ~mode;
}
/**
* The source mode determining what metadata to load. Defaults to
* MODE_META | MODE_MAPPING | MODE_QUERY
.
*/
public int getSourceMode() {
return _sourceMode;
}
/**
* The source mode determining what metadata to load. Defaults to
* MODE_META | MODE_MAPPING | MODE_QUERY
.
*/
public void setSourceMode(int mode) {
_sourceMode = mode;
}
/**
* The source mode determining what metadata to load. Defaults to
* MODE_META | MODE_MAPPING | MODE_QUERY
.
*/
public void setSourceMode(int mode, boolean on) {
if (mode == MODE_NONE)
_sourceMode = mode;
else if (on)
_sourceMode |= mode;
else
_sourceMode &= ~mode;
}
/**
* Sets whether this repository will load all known persistent classes at initialization.
* Defaults to false.
*/
public boolean getPreload() {
return _preload;
}
/**
* Sets whether this repository will load all known persistent classes at initialization.
* Defaults to false.
*/
public void setPreload(boolean l) {
_preload = l;
}
/**
* If the openjpa.MetaDataRepository plugin value Preload=true is set, this method will load all
* MetaData for all persistent classes and will remove locking from this class.
*/
public synchronized void preload() {
if (!_preload) {
return;
}
// If pooling EMFs, this method may be invoked more than once. Only perform this work once.
if (_preloadComplete) {
return;
}
MultiClassLoader multi = AccessController.doPrivileged(J2DoPrivHelper.newMultiClassLoaderAction());
multi.addClassLoader(AccessController.doPrivileged(J2DoPrivHelper.getContextClassLoaderAction()));
multi.addClassLoader(AccessController.doPrivileged(J2DoPrivHelper
.getClassLoaderAction(MetaDataRepository.class)));
// If a ClassLoader was passed into Persistence.createContainerEntityManagerFactory on the PersistenceUnitInfo
// we need to add that loader to the chain of classloaders
ClassResolver resolver = _conf.getClassResolverInstance();
if (resolver != null) {
ClassLoader cl = resolver.getClassLoader(null, null);
if (cl != null) {
multi.addClassLoader(cl);
}
}
Set classes = getPersistentTypeNames(false, multi);
if (classes == null || classes.size() == 0) {
throw new MetaDataException(_loc.get("repos-initializeEager-none"));
}
if (_log.isTraceEnabled()) {
_log.trace(_loc.get("repos-initializeEager-found", classes));
}
List> loaded = new ArrayList<>();
for (String c : classes) {
try {
Class> cls = AccessController.doPrivileged((J2DoPrivHelper.getForNameAction(c, true, multi)));
loaded.add(cls);
// This call may be unnecessary?
_factory.load(cls, MODE_ALL, multi);
} catch (PrivilegedActionException pae) {
throw new MetaDataException(_loc.get("repos-initializeEager-error"), pae);
}
}
resolveAll(multi);
// Preload XML MetaData
for (Class> cls : loaded) {
ClassMetaData cmd = getCachedMetaData(cls);
if (cmd != null) {
getXMLMetaData(cls);
for (FieldMetaData fmd : cmd.getFields()) {
getXMLMetaData(fmd.getDeclaredType());
}
}
}
// Hook in this class as a listener and process registered classes list to populate _aliases
// list.
PCRegistry.addRegisterClassListener(this);
processRegisteredClasses(multi);
_locking = false;
_preloadComplete = true;
}
/**
* Return the metadata for the given class.
*
* @param cls
* the class to retrieve metadata for
* @param envLoader
* the environmental class loader, if any
* @param mustExist
* if true, throws a {@link MetaDataException} if no metadata is found
*/
public ClassMetaData getMetaData(Class> cls, ClassLoader envLoader, boolean mustExist) {
if (_locking) {
synchronized(this){
return getMetaDataInternal(cls, envLoader, mustExist);
}
} else {
return getMetaDataInternal(cls, envLoader, mustExist);
}
}
private ClassMetaData getMetaDataInternal(Class> cls, ClassLoader envLoader, boolean mustExist) {
ClassMetaData meta = getMetaDataInternal(cls, envLoader);
if (meta == null) {
if (cls != null && DynamicPersistenceCapable.class.isAssignableFrom(cls))
cls = cls.getSuperclass();
// if cls is a generated interface, use the user interface
// to locate metadata
if (cls != null && _implGen != null && _implGen.isImplType(cls))
cls = _implGen.toManagedInterface(cls);
meta = getMetaDataInternal(cls, envLoader);
}
if (meta == null && mustExist) {
if (cls != null && !ImplHelper.isManagedType(_conf, cls))
throw new MetaDataException(_loc.get("no-meta-notpc", cls)).setFatal(false);
Set pcNames = getPersistentTypeNames(false, envLoader);
if (pcNames != null && pcNames.size() > 0)
throw new MetaDataException(_loc.get("no-meta-types", cls, pcNames));
throw new MetaDataException(_loc.get("no-meta", cls));
}
resolve(meta);
return meta;
}
/**
* Return the metadata for the given alias name.
*
* @param alias
* the alias to class to retrieve metadata for
* @param envLoader
* the environmental class loader, if any
* @param mustExist
* if true, throws a {@link MetaDataException} if no metadata is found
* @see ClassMetaData#getTypeAlias
*/
public ClassMetaData getMetaData(String alias, ClassLoader envLoader, boolean mustExist) {
if (alias == null && mustExist)
throw new MetaDataException(_loc.get("no-alias-meta", alias, _aliases));
if (alias == null)
return null;
// check cache
processRegisteredClasses(envLoader);
List> classList = _aliases.get(alias);
// multiple classes may have been defined with the same alias: we
// will filter by checking against the current list of the
// persistent types and filter based on which classes are loadable
// via the current environment's ClassLoader
Set pcNames = getPersistentTypeNames(false, envLoader);
Class> cls = null;
for (int i = 0; classList != null && i < classList.size(); i++) {
Class> c = classList.get(i);
try {
// re-load the class in the current environment loader so
// that we can handle redeployment of the same class name
Class> nc = Class.forName(c.getName(), false, envLoader);
// if we have specified a list of persistent clases,
// also check to ensure that the class is in that list
if (pcNames == null || pcNames.size() == 0 || pcNames.contains(nc.getName())) {
cls = nc;
if (!classList.contains(cls))
classList.add(cls);
break;
}
} catch (Throwable t) {
// this happens when the class is not loadable by
// the environment class loader, so it was probably
// listed elsewhere; also ignore linkage failures and
// other class loading problems
}
}
if (cls != null)
return getMetaData(cls, envLoader, mustExist);
// maybe this is some type we've seen but just isn't valid
if (_aliases.containsKey(alias)) {
if (mustExist)
throwNoRegisteredAlias(alias);
return null;
}
// We need to synchronize on _aliases because a ConcurrentModificationException can if there
// is a thread in getAliasNames() AND this class isn't using any locking.
synchronized (_aliases) {
// record that this is an invalid type
_aliases.put(alias, null);
}
if (!mustExist)
return null;
return throwNoRegisteredAlias(alias);
}
private ClassMetaData throwNoRegisteredAlias(String alias) {
String close = getClosestAliasName(alias);
if (close != null)
throw new MetaDataException(_loc.get("no-alias-meta-hint", alias, _aliases, close));
else
throw new MetaDataException(_loc.get("no-alias-meta", alias, _aliases));
}
/**
* @return the nearest match to the specified alias name
* @since 1.1.0
*/
public String getClosestAliasName(String alias) {
Collection aliases = getAliasNames();
return StringDistance.getClosestLevenshteinDistance(alias, aliases);
}
/**
* @return the registered alias names
* @since 1.1.0
*/
public Collection getAliasNames() {
if (_locking) {
synchronized (_aliases) {
return getAliasNamesInternal();
}
} else {
return getAliasNamesInternal();
}
}
private Collection getAliasNamesInternal() {
Collection aliases = new HashSet<>();
for(Map.Entry>> e : _aliases.entrySet()){
if (e.getValue() != null) {
aliases.add(e.getKey());
}
}
return aliases;
}
/**
* Internal method to get the metadata for the given class, without resolving it.
*/
private ClassMetaData getMetaDataInternal(Class> cls, ClassLoader envLoader) {
if (cls == null)
return null;
// check cache for existing metadata, or give up if no metadata and
// our list of configured persistent types doesn't include the class
ClassMetaData meta = _metas.get(cls);
if (meta != null && ((meta.getSourceMode() & MODE_META) != 0 || (_sourceMode & MODE_META) == 0))
return meta;
// if runtime, cut off search if not in pc list. we don't do this at
// dev time so that user can manipulate persistent classes he's writing
// before adding them to the list
if ((_validate & VALIDATE_RUNTIME) != 0) {
Set pcNames = getPersistentTypeNames(false, envLoader);
if (pcNames != null && !pcNames.contains(cls.getName()))
return meta;
}
if (meta == null) {
// check to see if maybe we know this class has no metadata
if (_metas.containsKey(cls))
return null;
// make sure this isn't an obviously bad class
if (cls.isPrimitive() || cls.getName().startsWith("java.") || cls == PersistenceCapable.class)
return null;
// designed to get around jikes 1.17 / JDK1.5 issue where static
// initializers are not invoked when a class is referenced, so the
// class never registers itself with the system
if ((_validate & VALIDATE_RUNTIME) != 0) {
try {
Class.forName(cls.getName(), true, AccessController.doPrivileged(J2DoPrivHelper
.getClassLoaderAction(cls)));
} catch (Throwable t) {
}
}
}
// not in cache: load metadata or mappings depending on source mode.
// loading metadata might also load mappings, but doesn't have to
int mode = 0;
if ((_sourceMode & MODE_META) != 0)
mode = _sourceMode & ~MODE_MAPPING;
else if ((_sourceMode & MODE_MAPPING) == 0)
mode = _sourceMode;
if (mode != MODE_NONE) {
if (_log.isTraceEnabled())
_log.trace(_loc.get("load-cls", cls, toModeString(mode)));
_factory.load(cls, mode, envLoader);
}
// check cache again
if (meta == null)
meta = _metas.get(cls);
if (meta != null && ((meta.getSourceMode() & MODE_META) != 0 || (_sourceMode & MODE_META) == 0))
return meta;
// record that this class has no metadata; checking for this later
// speeds things up in environments with slow class loading
// like appservers
if (meta != null)
removeMetaData(meta);
_metas.put(cls, null);
return null;
}
/**
* Return a string representation of the given mode flags.
*/
private static String toModeString(int mode) {
StringBuilder buf = new StringBuilder(31);
if ((mode & MODE_META) != 0)
buf.append("[META]");
if ((mode & MODE_QUERY) != 0)
buf.append("[QUERY]");
if ((mode & MODE_MAPPING) != 0)
buf.append("[MAPPING]");
if ((mode & MODE_MAPPING_INIT) != 0)
buf.append("[MAPPING_INIT]");
return buf.toString();
}
/**
* Prepare metadata for mapping resolution. This method might map parts of the metadata that
* don't rely on other classes being mapped, but that other classes might rely on during their
* own mapping (for example, primary key fields). By default, this method only calls
* {@link ClassMetaData#defineSuperclassFields}.
*/
protected void prepareMapping(ClassMetaData meta) {
meta.defineSuperclassFields(false);
}
/**
* Resolve the given metadata if needed. There are three goals:
*
* - Make sure no unresolved metadata gets back to the client.
* - Avoid infinite reentrant calls for mutually-dependent metadatas by allowing unresolved
* metadata to be returned to other metadatas.
* - Always make sure the superclass metadata is resolved before the subclass metadata so that
* the subclass can access the super's list of fields.
*
* Note that the code calling this method is synchronized, so this method doesn't have to be.
*/
private void resolve(ClassMetaData meta) {
// return anything that has its metadata resolved, because that means
// it is either fully resolved or must at least be in the process of
// resolving mapping, etc since we do that right after meta resolve
if (meta == null || _resMode == MODE_NONE || (meta.getResolve() & MODE_META) != 0)
return;
// resolve metadata
List resolved = resolveMeta(meta);
if (resolved == null)
return;
// load mapping data
for (ClassMetaData data : resolved) {
loadMapping(data);
}
for (ClassMetaData metaData : resolved) {
preMapping(metaData);
}
// resolve mappings
boolean err = true;
if ((_resMode & MODE_MAPPING) != 0)
for (ClassMetaData classMetaData : resolved) {
err &= resolveMapping(classMetaData);
}
// throw errors encountered
// OPENJPA-1535 Always throw a MetaDataException because callers
// of loadRegisteredClassMetaData expect only MetaDataException
// to be thrown.
if (err && !_errs.isEmpty()) {
RuntimeException re;
if ((_errs.size() == 1) && (_errs.get(0) instanceof MetaDataException)) {
re = _errs.get(0);
} else {
re = new MetaDataException(_loc.get("resolve-errs"))
.setNestedThrowables(_errs
.toArray(new Exception[_errs.size()]));
}
_errs.clear();
throw re;
}
}
/**
* Resolve metadata mode, returning list of processed metadadatas, or null if we're still in the
* process of resolving other metadatas.
*/
private List resolveMeta(ClassMetaData meta) {
if (meta.getPCSuperclass() == null) {
// set superclass
Class> sup = meta.getDescribedType().getSuperclass();
ClassMetaData supMeta;
while (sup != null && sup != Object.class) {
supMeta = getMetaData(sup, meta.getEnvClassLoader(), false);
if (supMeta != null) {
meta.setPCSuperclass(sup);
meta.setPCSuperclassMetaData(supMeta);
break;
} else
sup = sup.getSuperclass();
}
if (meta.getDescribedType().isInterface()) {
Class>[] sups = meta.getDescribedType().getInterfaces();
for (Class> aClass : sups) {
supMeta = getMetaData(aClass, meta.getEnvClassLoader(), false);
if (supMeta != null) {
meta.setPCSuperclass(sup);
meta.setPCSuperclassMetaData(supMeta);
break;
}
}
}
if (_log.isTraceEnabled())
_log.trace(_loc.get("assigned-sup", meta, meta.getPCSuperclass()));
}
// resolve relation primary key fields for mapping dependencies
FieldMetaData[] fmds = meta.getDeclaredFields();
for (FieldMetaData fmd : fmds)
if (fmd.isPrimaryKey())
getMetaData(fmd.getDeclaredType(), meta.getEnvClassLoader(), false);
// resolve metadata; if we're not in the process of resolving
// others, this will return the set of interrelated metas that
// resolved
return processBuffer(meta, _resolving, MODE_META);
}
/**
* Load mapping information for the given metadata.
*/
private void loadMapping(ClassMetaData meta) {
if ((meta.getResolve() & MODE_MAPPING) != 0)
return;
// load mapping information
if ((meta.getSourceMode() & MODE_MAPPING) == 0 && (_sourceMode & MODE_MAPPING) != 0) {
// embedded-only metadata doesn't have mapping, so always loaded
if (meta.isEmbeddedOnly())
meta.setSourceMode(MODE_MAPPING, true);
else {
// load mapping data
int mode = _sourceMode & ~MODE_META;
if (_log.isTraceEnabled())
_log.trace(_loc.get("load-mapping", meta, toModeString(mode)));
try {
_factory.load(meta.getDescribedType(), mode, meta.getEnvClassLoader());
} catch (RuntimeException re) {
removeMetaData(meta);
_errs.add(re);
}
}
}
}
/**
* Pre-mapping preparation.
*/
private void preMapping(ClassMetaData meta) {
if ((meta.getResolve() & MODE_MAPPING) != 0)
return;
// prepare mappings for resolve; if not resolving mappings, then
// make sure any superclass fields defined in metadata are resolved
try {
if ((_resMode & MODE_MAPPING) != 0) {
if (_log.isTraceEnabled())
_log.trace(_loc.get("prep-mapping", meta));
prepareMapping(meta);
} else
meta.defineSuperclassFields(false);
} catch (RuntimeException re) {
removeMetaData(meta);
_errs.add(re);
}
}
/**
* Resolve and initialize mapping.
*
* @return false if we're still in the process of resolving mappings
*/
private boolean resolveMapping(ClassMetaData meta) {
List mapped = processBuffer(meta, _mapping, MODE_MAPPING);
if (mapped == null)
return false;
// initialize mapping for runtime use
if ((_resMode & MODE_MAPPING_INIT) != 0) {
for (ClassMetaData classMetaData : mapped) {
meta = classMetaData;
try {
meta.resolve(MODE_MAPPING_INIT);
}
catch (RuntimeException re) {
removeMetaData(meta);
_errs.add(re);
}
}
}
return true;
}
/**
* Process the given metadata and the associated buffer.
*/
private List processBuffer(ClassMetaData meta, List buffer, int mode) {
// add the metadata to the buffer unless an instance for the same entity
// is already there
for (ClassMetaData cmd : buffer)
if (cmd.getDescribedType().equals(meta.getDescribedType()))
return null;
// if we're already processing a metadata, just buffer this one; when
// the initial metadata finishes processing, we traverse the buffer
// and process all the others that were introduced during reentrant
// calls
buffer.add(meta);
if (buffer.size() != 1)
return null;
// continually pop a metadata and process it until we run out; note
// that each processing call might place more metas in the buffer as
// one class tries to access metadata for another
ClassMetaData buffered;
List processed = new ArrayList<>(5);
while (!buffer.isEmpty()) {
buffered = buffer.get(0);
try {
buffered.resolve(mode);
processed.add(buffered);
buffer.remove(buffered);
} catch (RuntimeException re) {
_errs.add(re);
// any exception during resolution of one type means we can't
// resolve any of the related types, so clear buffer. this also
// ensures that if two types relate to each other and one
// dies, we don't get into infinite cycles
for (ClassMetaData cmd : buffer) {
removeMetaData(cmd);
if (cmd != buffered) {
_errs.add(new MetaDataException(_loc.get("prev-errs", cmd, buffered)));
}
}
buffer.clear();
}
}
return processed;
}
/**
* Return all the metadata instances currently in the repository.
*/
public ClassMetaData[] getMetaDatas() {
if (_locking) {
synchronized(this){
return getMetaDatasInternal();
}
} else {
return getMetaDatasInternal();
}
}
private ClassMetaData[] getMetaDatasInternal() {
// prevent concurrent mod errors when resolving one metadata
// introduces others
ClassMetaData[] metas = _metas.values().toArray(new ClassMetaData[_metas.size()]);
for (ClassMetaData classMetaData : metas)
if (classMetaData != null)
getMetaData(classMetaData.getDescribedType(), classMetaData.getEnvClassLoader(), true);
List resolved = new ArrayList<>(_metas.size());
for (ClassMetaData meta : _metas.values()) {
if (meta != null)
resolved.add(meta);
}
metas = resolved.toArray(newClassMetaDataArray(resolved.size()));
Arrays.sort(metas);
return metas;
}
/**
* Return the cached metadata for the given class, without any resolution. Return null if none.
*/
public ClassMetaData getCachedMetaData(Class> cls) {
return _metas.get(cls);
}
/**
* Create a new metadata, populate it with default information, add it to the repository, and
* return it. Use the default access type.
*/
public ClassMetaData addMetaData(Class> cls) {
return addMetaData(cls, AccessCode.UNKNOWN);
}
/**
* Create a new metadata, populate it with default information, add it to the repository, and
* return it.
*
* @param access
* the access type to use in populating metadata
*/
public ClassMetaData addMetaData(Class> cls, int access) {
return addMetaData(cls, access, false);
}
/**
* Create a new metadata, populate it with default information, add it to the repository, and
* return it.
*
* @param access
* the access type to use in populating metadata
*/
public ClassMetaData addMetaData(Class> cls, int access, boolean ignoreTransient) {
if (cls == null || cls.isPrimitive())
return null;
ClassMetaData meta = newClassMetaData(cls);
_factory.getDefaults().populate(meta, access, ignoreTransient);
// synchronize on this rather than the map, because all other methods
// that access _metas are synchronized on this
if (_locking) {
synchronized(this){
return metasPutInternal(cls, meta);
}
} else {
return metasPutInternal(cls, meta);
}
}
private ClassMetaData metasPutInternal(Class> cls, ClassMetaData meta){
if (_pawares.containsKey(cls))
throw new MetaDataException(_loc.get("pc-and-aware", cls));
_metas.put(cls, meta);
return meta;
}
/**
* Create a new class metadata instance.
*/
protected ClassMetaData newClassMetaData(Class> type) {
return new ClassMetaData(type, this);
}
/**
* Create a new array of the proper class metadata subclass.
*/
protected ClassMetaData[] newClassMetaDataArray(int length) {
return new ClassMetaData[length];
}
/**
* Create a new field metadata instance.
*/
protected FieldMetaData newFieldMetaData(String name, Class> type, ClassMetaData owner) {
return new FieldMetaData(name, type, owner);
}
/**
* Create a new array of the proper field metadata subclass.
*/
protected FieldMetaData[] newFieldMetaDataArray(int length) {
return new FieldMetaData[length];
}
/**
* Create a new array of the proper xml class metadata subclass.
*/
protected XMLMetaData[] newXMLClassMetaDataArray(int length) {
return new XMLClassMetaData[length];
}
/**
* Create a new embedded class metadata instance.
*/
protected ClassMetaData newEmbeddedClassMetaData(ValueMetaData owner) {
return new ClassMetaData(owner);
}
/**
* Create a new value metadata instance.
*/
protected ValueMetaData newValueMetaData(FieldMetaData owner) {
return new ValueMetaDataImpl(owner);
}
/**
* Create an {@link Order} for the given field and declaration. This method delegates to
* {@link #newRelatedFieldOrder} and {@link #newValueOrder(FieldMetaData, boolean)} by default.
*/
protected Order newOrder(FieldMetaData owner, String name, boolean asc) {
// paths can start with (or equal) '#element'
if (name.startsWith(Order.ELEMENT))
name = name.substring(Order.ELEMENT.length());
if (name.length() == 0)
return newValueOrder(owner, asc);
// next token should be '.'
if (name.charAt(0) == '.')
name = name.substring(1);
// related field
ClassMetaData meta = owner.getElement().getTypeMetaData();
if (meta == null)
throw new MetaDataException(_loc.get("nonpc-field-orderable", owner, name));
FieldMetaData rel = getOrderByField(meta, name);
if (rel == null)
throw new MetaDataException(_loc.get("bad-field-orderable", owner, name));
return newRelatedFieldOrder(owner, rel, asc);
}
public FieldMetaData getOrderByField(ClassMetaData meta, String orderBy) {
FieldMetaData field = meta.getField(orderBy);
if (field != null)
return field;
int dotIdx = orderBy.indexOf(".");
if (dotIdx == -1)
return null;
String fieldName = orderBy.substring(0, dotIdx);
FieldMetaData field1 = meta.getField(fieldName);
if (field1 == null)
return null;
ClassMetaData meta1 = field1.getEmbeddedMetaData();
if (meta1 == null)
return null;
String mappedBy1 = orderBy.substring(dotIdx + 1);
return getOrderByField(meta1, mappedBy1);
}
/**
* Order by the field value.
*/
protected Order newValueOrder(FieldMetaData owner, boolean asc) {
return new InMemoryValueOrder(asc, getConfiguration());
}
/**
* Order by a field of the related type.
*/
protected Order newRelatedFieldOrder(FieldMetaData owner, FieldMetaData rel, boolean asc) {
return new InMemoryRelatedFieldOrder(rel, asc, getConfiguration());
}
/**
* Create an array of orders of the given size.
*/
protected Order[] newOrderArray(int size) {
return new Order[size];
}
/**
* Remove a metadata instance from the repository.
*
* @return true if removed, false if not in this repository
*/
public boolean removeMetaData(ClassMetaData meta) {
if (meta == null)
return false;
return removeMetaData(meta.getDescribedType());
}
/**
* Remove a metadata instance from the repository.
*
* @return true if removed, false if not in this repository
*/
public boolean removeMetaData(Class> cls) {
if(_locking){
synchronized(this){
return removeMetaDataInternal(cls);
}
}else{
return removeMetaDataInternal(cls);
}
}
private boolean removeMetaDataInternal(Class> cls) {
if (cls == null)
return false;
if (_metas.remove(cls) != null) {
Class> impl = _ifaces.remove(cls);
if (impl != null)
_metas.remove(impl);
return true;
}
return false;
}
/**
* Add the given metadata as declared interface implementation.
*/
void addDeclaredInterfaceImpl(ClassMetaData meta, Class> iface) {
if (_locking) {
synchronized (_impls) {
addDeclaredInterfaceImplInternal(meta, iface);
}
} else {
addDeclaredInterfaceImplInternal(meta, iface);
}
}
private void addDeclaredInterfaceImplInternal(ClassMetaData meta, Class> iface) {
Collection> vals = _impls.get(iface);
// check to see if the superclass already declares to avoid dups
if (vals != null) {
ClassMetaData sup = meta.getPCSuperclassMetaData();
for (; sup != null; sup = sup.getPCSuperclassMetaData())
if (vals.contains(sup.getDescribedType()))
return;
}
addToCollection(_impls, iface, meta.getDescribedType(), false);
}
/**
* Set the implementation for the given managed interface.
*/
void setInterfaceImpl(ClassMetaData meta, Class> impl) {
if (_locking) {
synchronized (this) {
setInterfaceImplInternal(meta, impl);
}
} else {
setInterfaceImplInternal(meta, impl);
}
}
private void setInterfaceImplInternal(ClassMetaData meta, Class> impl) {
if (!meta.isManagedInterface())
throw new MetaDataException(_loc.get("not-managed-interface", meta, impl));
_ifaces.put(meta.getDescribedType(), impl);
addDeclaredInterfaceImpl(meta, meta.getDescribedType());
ClassMetaData sup = meta.getPCSuperclassMetaData();
while (sup != null) {
// record superclass interface info while we can as well as we
// will only register concrete superclass in PCRegistry
sup.clearSubclassCache();
addToCollection(_subs, sup.getDescribedType(), impl, true);
sup = sup.getPCSuperclassMetaData();
}
}
InterfaceImplGenerator getImplGenerator() {
return _implGen;
}
/**
* Return the least-derived class metadata for the given application identity object.
*
* @param oid
* the oid to get the metadata for
* @param envLoader
* the environmental class loader, if any
* @param mustExist
* if true, throws a {@link MetaDataException} if no metadata is found
*/
public ClassMetaData getMetaData(Object oid, ClassLoader envLoader, boolean mustExist) {
if (oid == null && mustExist)
throw new MetaDataException(_loc.get("no-oid-meta", oid, "?", _oids.toString()));
if (oid == null)
return null;
if (oid instanceof OpenJPAId) {
Class> cls = ((OpenJPAId) oid).getType();
return getMetaData(cls, envLoader, mustExist);
}
// check cache
processRegisteredClasses(envLoader);
Class> cls = _oids.get(oid.getClass());
if (cls != null)
return getMetaData(cls, envLoader, mustExist);
// maybe this is some type we've seen but just isn't valid
if (_oids.containsKey(oid.getClass())) {
if (mustExist)
throw new MetaDataException(_loc.get("no-oid-meta", oid, oid.getClass(), _oids));
return null;
}
// if still not match, register any classes that look similar to the
// oid class and check again
resolveIdentityClass(oid);
if (processRegisteredClasses(envLoader).length > 0) {
cls = _oids.get(oid.getClass());
if (cls != null)
return getMetaData(cls, envLoader, mustExist);
}
// record that this is an invalid type
_oids.put(oid.getClass(), null);
if (!mustExist)
return null;
throw new MetaDataException(_loc.get("no-oid-meta", oid, oid.getClass(), _oids)).setFailedObject(oid);
}
/**
* Make some guesses about the name of a target class for an unknown application identity class.
*/
private void resolveIdentityClass(Object oid) {
if (oid == null)
return;
Class> oidClass = oid.getClass();
if (_log.isTraceEnabled())
_log.trace(_loc.get("resolve-identity", oidClass));
ClassLoader cl = AccessController.doPrivileged(J2DoPrivHelper.getClassLoaderAction(oidClass));
String className;
while (oidClass != null && oidClass != Object.class) {
className = oidClass.getName();
// we take a brute-force approach: try to load all the class'
// substrings. this will handle the following common naming cases:
//
// com.company.MyClass$ID -> com.company.MyClass
// com.company.MyClassId -> com.company.MyClass
// com.company.MyClassOid -> com.company.MyClass
// com.company.MyClassPK -> com.company.MyClass
//
// this isn't the fastest thing possible, but this method will
// only be called once per JVM per unknown app id class
for (int i = className.length(); i > 1; i--) {
if (className.charAt(i - 1) == '.')
break;
try {
Class.forName(className.substring(0, i), true, cl);
} catch (Exception e) {
} // consume all exceptions
}
// move up the OID hierarchy
oidClass = oidClass.getSuperclass();
}
}
/**
* Return all least-derived metadatas with some mapped assignable type that implement the given
* class.
*
* @param cls
* the class or interface to retrieve implementors for
* @param envLoader
* the environmental class loader, if any
* @param mustExist
* if true, throws a {@link MetaDataException} if no metadata is found
*/
public ClassMetaData[] getImplementorMetaDatas(Class> cls, ClassLoader envLoader, boolean mustExist) {
if (cls == null && mustExist)
throw new MetaDataException(_loc.get("no-meta", cls));
if (cls == null)
return EMPTY_METAS;
// get impls of given interface / abstract class
loadRegisteredClassMetaData(envLoader);
Collection> vals = _impls.get(cls);
ClassMetaData[] mapped = null;
if (vals != null) {
if (_locking) {
synchronized (vals) {
mapped = getImplementorMetaDatasInternal(vals, envLoader, mustExist);
}
} else {
mapped = getImplementorMetaDatasInternal(vals, envLoader, mustExist);
}
}
if (mapped == null && mustExist)
throw new MetaDataException(_loc.get("no-meta", cls));
if (mapped == null)
return EMPTY_METAS;
return mapped;
}
private ClassMetaData[] getImplementorMetaDatasInternal(Collection> classes, ClassLoader envLoader,
boolean mustExist) {
Collection mapped = new ArrayList<>(classes.size());
ClassMetaData meta = null;
for (Class> c : classes) {
meta = getMetaData(c, envLoader, true);
if (meta.isMapped() || meta.getMappedPCSubclassMetaDatas().length > 0) {
mapped.add(meta);
}
}
return mapped.toArray(new ClassMetaData[mapped.size()]);
}
/**
* Gets the metadata corresponding to the given persistence-aware class. Returns null, if the
* given class is not registered as persistence-aware.
*/
public NonPersistentMetaData getPersistenceAware(Class> cls) {
return _pawares.get(cls);
}
public boolean skipMetadata(final Class> cls) {
if (cls == null || cls.isEnum()) {
return true;
}
if (_typesWithoutEnhancement == null) {
return false;
}
return _typesWithoutEnhancement.stream().anyMatch(it -> it.isAssignableFrom(cls));
}
/**
* Gets all the metadatas for persistence-aware classes
*
* @return empty array if no class has been registered as pers-aware
*/
public NonPersistentMetaData[] getPersistenceAwares() {
if (_locking) {
synchronized (_pawares) {
return getPersistenceAwaresInternal();
}
} else {
return getPersistenceAwaresInternal();
}
}
private NonPersistentMetaData[] getPersistenceAwaresInternal() {
if (_pawares.isEmpty())
return EMPTY_NON_PERSISTENT;
return _pawares.values().toArray(new NonPersistentMetaData[_pawares.size()]);
}
/**
* Add the given class as persistence-aware.
*
* @param cls
* non-null and must not alreaddy be added as persitence-capable
*/
public NonPersistentMetaData addPersistenceAware(Class> cls) {
if (cls == null)
return null;
if (_locking) {
synchronized (this) {
return addPersistenceAwareInternal(cls);
}
} else {
return addPersistenceAwareInternal(cls);
}
}
private NonPersistentMetaData addPersistenceAwareInternal(Class> cls) {
if (_pawares.containsKey(cls))
return _pawares.get(cls);
if (getCachedMetaData(cls) != null)
throw new MetaDataException(_loc.get("pc-and-aware", cls));
NonPersistentMetaData meta =
new NonPersistentMetaData(cls, this, NonPersistentMetaData.TYPE_PERSISTENCE_AWARE);
_pawares.put(cls, meta);
return meta;
}
/**
* Remove a persitence-aware class from the repository
*
* @return true if removed
*/
public boolean removePersistenceAware(Class> cls) {
return _pawares.remove(cls) != null;
}
/**
* Gets the metadata corresponding to the given non-mapped interface. Returns null, if the given
* interface is not registered as persistence-aware.
*/
public NonPersistentMetaData getNonMappedInterface(Class> iface) {
return _nonMapped.get(iface);
}
/**
* Gets the corresponding metadatas for all registered, non-mapped interfaces
*
* @return empty array if no non-mapped interface has been registered.
*/
public NonPersistentMetaData[] getNonMappedInterfaces() {
if (_locking) {
synchronized (_nonMapped) {
return getNonMappedInterfacesInternal();
}
} else {
return getNonMappedInterfacesInternal();
}
}
private NonPersistentMetaData[] getNonMappedInterfacesInternal() {
if (_nonMapped.isEmpty())
return EMPTY_NON_PERSISTENT;
return _nonMapped.values().toArray(new NonPersistentMetaData[_nonMapped.size()]);
}
/**
* Add the given non-mapped interface to the repository.
*
* @param iface
* the non-mapped interface
*/
public NonPersistentMetaData addNonMappedInterface(Class> iface) {
if (iface == null)
return null;
if (!iface.isInterface())
throw new MetaDataException(_loc.get("not-non-mapped", iface));
if (_locking) {
synchronized (this) {
return addNonMappedInterfaceInternal(iface);
}
} else {
return addNonMappedInterfaceInternal(iface);
}
}
private NonPersistentMetaData addNonMappedInterfaceInternal(Class> iface) {
if (_nonMapped.containsKey(iface))
return _nonMapped.get(iface);
if (getCachedMetaData(iface) != null)
throw new MetaDataException(_loc.get("non-mapped-pc", iface));
NonPersistentMetaData meta =
new NonPersistentMetaData(iface, this, NonPersistentMetaData.TYPE_NON_MAPPED_INTERFACE);
_nonMapped.put(iface, meta);
return meta;
}
/**
* Remove a non-mapped interface from the repository
*
* @return true if removed
*/
public boolean removeNonMappedInterface(Class> iface) {
return _nonMapped.remove(iface) != null;
}
/**
* Clear the cache of parsed metadata. This method also clears the internal
* {@link MetaDataFactory MetaDataFactory}'s cache.
*/
public void clear() {
if (_log.isTraceEnabled())
_log.trace(_loc.get("clear-repos", this));
if (_locking) {
synchronized (this) {
clearInternal();
}
} else {
clearInternal();
}
}
private void clearInternal(){
// Recreating these datastructures is probably faster than calling clear. Future change?
_metas.clear();
_oids.clear();
_subs.clear();
_impls.clear();
_queries.clear();
_seqs.clear();
_registered.clear();
_factory.clear();
_aliases.clear();
_pawares.clear();
_nonMapped.clear();
_metaStringMap.clear();
}
/**
* Return the set of configured persistent classes, or null if the user did not configure any.
*
* @param devpath
* if true, search for metadata files in directories in the classpath if no classes
* are configured explicitly
* @param envLoader
* the class loader to use, or null for default
*/
public Set getPersistentTypeNames(boolean devpath, ClassLoader envLoader) {
if (_locking) {
synchronized (this) {
return getPersistentTypeNamesInternal(devpath, envLoader);
}
} else {
return getPersistentTypeNamesInternal(devpath, envLoader);
}
}
private Set getPersistentTypeNamesInternal(boolean devpath, ClassLoader envLoader) {
return _factory.getPersistentTypeNames(devpath, envLoader);
}
/**
* Load the persistent classes named in configuration.
* This ensures that all subclasses and application identity classes of
* each type are known in advance, without having to rely on the
* application loading the classes before performing operations that
* might involve them.
*
* @param devpath if true, search for metadata files in directories
* in the classpath if the no classes are configured explicitly
* @param envLoader the class loader to use, or null for default
* @return the loaded classes, or empty collection if none
*/
public Collection> loadPersistentTypes(boolean devpath, ClassLoader envLoader) {
return loadPersistentTypes(devpath, envLoader, false);
}
/**
* Load the persistent classes named in configuration. This ensures that all subclasses and
* application identity classes of each type are known in advance, without having to rely on the
* application loading the classes before performing operations that might involve them.
*
* @param devpath
* if true, search for metadata files in directories in the classpath if the no
* classes are configured explicitly
* @param envLoader
* the class loader to use, or null for default
* @param mustExist
* if true then empty list of classes or any unloadable but specified class will
* raise an exception.
* @return the loaded classes, or empty collection if none
*/
public Collection> loadPersistentTypes(boolean devpath, ClassLoader envLoader, boolean mustExist) {
if (_locking) {
synchronized (this) {
return loadPersistentTypesInternal(devpath, envLoader, mustExist);
}
} else {
return loadPersistentTypesInternal(devpath, envLoader, mustExist);
}
}
private Collection> loadPersistentTypesInternal(boolean devpath, ClassLoader envLoader,
boolean mustExist) {
Set names = getPersistentTypeNames(devpath, envLoader);
if (names == null || names.isEmpty()) {
if (!mustExist)
return Collections.emptyList();
else
throw new MetaDataException(_loc.get("eager-no-class-found"));
}
// attempt to load classes so that they get processed
ClassLoader clsLoader = _conf.getClassResolverInstance().getClassLoader(getClass(), envLoader);
List> classes = new ArrayList<>(names.size());
Class> cls;
for (String className : names) {
cls = classForName(className, clsLoader);
if (_factory.isMetaClass(cls)) {
setMetaModel(cls);
continue;
}
if (skipMetadata(cls)) {
continue;
}
if (cls != null) {
classes.add(cls);
// if the class is an interface, load its metadata to kick
// off the impl generator
if (cls.isInterface())
getMetaData(cls, clsLoader, false);
} else if (cls == null && mustExist) {
throw new MetaDataException(_loc.get("eager-class-not-found", className));
}
}
return classes;
}
/**
* Return the class for the given name, or null if not loadable.
*/
private Class> classForName(String name, ClassLoader loader) {
try {
return Class.forName(name, true, loader);
} catch (Exception e) {
if ((_validate & VALIDATE_RUNTIME) != 0) {
if (_log.isWarnEnabled())
_log.warn(_loc.get("bad-discover-class", name, loader));
} else if (_log.isInfoEnabled())
_log.info(_loc.get("bad-discover-class", name, loader));
if (_log.isTraceEnabled())
_log.trace(e);
} catch (NoSuchMethodError nsme) {
if (nsme.getMessage().indexOf(".pc") == -1)
throw nsme;
// if the error is about a method that uses the PersistenceCapable
// 'pc' method prefix, perform some logging and continue. This
// probably just means that the class is not yet enhanced.
if ((_validate & VALIDATE_RUNTIME) != 0) {
if (_log.isWarnEnabled())
_log.warn(_loc.get("bad-discover-class", name, loader));
} else if (_log.isInfoEnabled())
_log.info(_loc.get("bad-discover-class", name, loader));
if (_log.isTraceEnabled())
_log.trace(nsme);
}
catch (NoClassDefFoundError ndcfe) {
throw ndcfe;
}
return null;
}
/**
* Return all known subclasses for the given class mapping. Note that this method only works
* during runtime when the repository is registered as a {@link RegisterClassListener}.
*/
Collection> getPCSubclasses(Class> cls) {
Collection> subs = _subs.get(cls);
if (subs == null)
return Collections.emptyList();
return subs;
}
// //////////////////////////////////////
// RegisterClassListener implementation
// //////////////////////////////////////
@Override
public void register(Class> cls) {
// buffer registered classes until an oid metadata request is made,
// at which point we'll parse everything in the buffer
synchronized (_registered) {
_registered.add(cls);
registerAlias(cls);
}
}
/**
* Parses the metadata for all registered classes.
*/
private void loadRegisteredClassMetaData(ClassLoader envLoader) {
Class>[] reg = processRegisteredClasses(envLoader);
for (Class> aClass : reg) {
try {
getMetaData(aClass, envLoader, false);
}
catch (MetaDataException me) {
if (_log.isWarnEnabled())
_log.warn(me);
}
}
}
/**
* Updates our data structures with the latest registered classes.
*
* This method is synchronized to make sure that all data structures are fully updated
* before other threads attempt to call this method
*/
synchronized Class>[] processRegisteredClasses(ClassLoader envLoader) {
Class>[] reg;
/*Synchronize `_registered` cache to block MetaDataRepository.register() from adding
* to the cache while we copy, causing a ConcurrentModificationException
*/
synchronized (_registered) {
if (_registered.isEmpty()) {
return EMPTY_CLASSES;
}
// copy into new collection to avoid concurrent mod errors on reentrant
// registrations
reg = _registered.toArray(new Class[_registered.size()]);
_registered.clear();
}
Collection pcNames = getPersistentTypeNames(false, envLoader);
Collection> failed = null;
for (Class> aClass : reg) {
// Don't process types that aren't listed by the user; it may belong to a different persistence unit.
if (pcNames != null && !pcNames.isEmpty() && !pcNames.contains(aClass.getName())) {
continue;
}
// If the compatibility option "filterPCRegistryClasses" is enabled, then verify that the type is
// accessible to the envLoader/Thread Context ClassLoader
if (_filterRegisteredClasses) {
Log log = (_conf == null) ? null : _conf.getLog(OpenJPAConfiguration.LOG_RUNTIME);
ClassLoader loadCL = (envLoader != null) ?
envLoader :
AccessController.doPrivileged(J2DoPrivHelper.getContextClassLoaderAction());
try {
Class> classFromAppClassLoader = Class.forName(aClass.getName(), true, loadCL);
if (!aClass.equals(classFromAppClassLoader)) {
// This is a class that belongs to a ClassLoader not associated with the Application,
// so it should be processed.
if (log != null && log.isTraceEnabled()) {
log.trace(
"Metadata Repository will ignore Class " + aClass.getName() +
", since it originated from a ClassLoader not associated with the application.");
}
continue;
}
}
catch (ClassNotFoundException cnfe) {
// Catch exception and log its occurrence, and permit MDR processing to continue to preserve
// original behavior.
if (log != null && log.isTraceEnabled()) {
log.trace("The Class " + aClass.getName() + " was identified as a persistent class " +
"by configuration, but the Class could not be found.");
}
}
}
checkEnhancementLevel(aClass);
try {
processRegisteredClass(aClass);
}
catch (Throwable t) {
if (!_conf.getRetryClassRegistration())
throw new MetaDataException(_loc.get("error-registered", aClass), t);
if (_log.isWarnEnabled())
_log.warn(_loc.get("failed-registered", aClass), t);
if (failed == null)
failed = new ArrayList<>();
failed.add(aClass);
}
}
if (failed != null) {
if (_locking) {
synchronized (_registered) {
_registered.addAll(failed);
}
} else {
_registered.addAll(failed);
}
}
return reg;
}
/**
* Updates our datastructures with the given registered class. Relies on the fact that a child
* class cannot register itself without also registering its parent class by specifying its
* persistence capable superclass in the registration event.
*/
private void processRegisteredClass(Class> cls) {
if (_log.isTraceEnabled())
_log.trace(_loc.get("process-registered", cls));
// update subclass lists; synchronize on this because accessing _metas
// requires it
Class> leastDerived = cls;
synchronized (this) {
ClassMetaData meta;
for (Class> anc = cls; (anc = PCRegistry.getPersistentSuperclass(anc)) != null;) {
addToCollection(_subs, anc, cls, true);
meta = _metas.get(anc);
if (meta != null)
meta.clearSubclassCache();
leastDerived = anc;
}
}
// update oid mappings if this is a base concrete class
Object oid = null;
try {
oid = PCRegistry.newObjectId(cls);
} catch (InternalException ie) {
// thrown for single field identity with null pk field value
}
if (oid != null) {
Class> existing = _oids.get(oid.getClass());
if (existing != null) {
// if there is already a class for this OID, then we know
// that multiple classes are using the same OID: therefore,
// put the least derived PC superclass into the map. This
// gets around the problem of an abstract PC superclass
// using application identity (since newObjectId
// will return null for abstract classes).
Class> sup = cls;
while (PCRegistry.getPersistentSuperclass(sup) != null)
sup = PCRegistry.getPersistentSuperclass(sup);
_oids.put(oid.getClass(), sup);
} else if (existing == null || cls.isAssignableFrom(existing))
_oids.put(oid.getClass(), cls);
}
// update mappings from interfaces and non-pc superclasses to
// pc implementing types
if (_locking) {
synchronized (_impls) {
updateImpls(cls, leastDerived, cls);
}
} else {
updateImpls(cls, leastDerived, cls);
}
// set alias for class
registerAlias(cls);
}
/**
* Register the given class to the list of known aliases.
* The alias is registered only if the class has been enhanced.
*
*/
void registerAlias(Class> cls) {
registerAlias(PCRegistry.getTypeAlias(cls), cls);
}
public void registerAlias(String alias, Class> cls) {
if (alias == null)
return;
try {
if (alias != null) {
List> classes = _aliases.get(alias);
if (classes == null)
classes = new ArrayList<>(3);
if (!classes.contains(cls)) {
classes.add(cls);
_aliases.put(alias, classes);
}
}
} catch (IllegalStateException ise) {
// the class has not been registered to PCRegistry
}
}
/**
* Update the list of implementations of base classes and interfaces.
*/
private void updateImpls(Class> cls, Class> leastDerived, Class> check) {
// allow users to query on common non-pc superclasses
Class> sup = check.getSuperclass();
if (leastDerived == cls && sup != null && sup != Object.class) {
addToCollection(_impls, sup, cls, false);
updateImpls(cls, leastDerived, sup);
}
// allow users to query on any implemented interfaces unless defaults
// say the user must implement persistent interfaces explicitly in meta
if (!_factory.getDefaults().isDeclaredInterfacePersistent())
return;
Class>[] ints = check.getInterfaces();
for (Class> anInt : ints) {
// don't map java-standard interfaces
if (anInt.getName().startsWith("java."))
continue;
// only map least-derived interface implementors
if (leastDerived == cls || isLeastDerivedImpl(anInt, cls)) {
addToCollection(_impls, anInt, cls, false);
updateImpls(cls, leastDerived, anInt);
}
}
}
/**
* Return true if the given class is the least-derived persistent implementor of the given
* interface, false otherwise.
*/
private boolean isLeastDerivedImpl(Class> inter, Class> cls) {
Class> parent = PCRegistry.getPersistentSuperclass(cls);
while (parent != null) {
if (Arrays.asList(parent.getInterfaces()).contains(inter))
return false;
parent = PCRegistry.getPersistentSuperclass(parent);
}
return true;
}
/**
* Add the given value to the collection cached in the given map under the given key.
*/
private void addToCollection(Map, Collection>> map,
Class> key, Class> value, boolean inheritance) {
if (_locking) {
synchronized (map) {
addToCollectionInternal(map, key, value, inheritance);
}
} else {
addToCollectionInternal(map, key, value, inheritance);
}
}
private void addToCollectionInternal(Map, Collection>> map,
Class> key, Class> value, boolean inheritance) {
Collection> coll = map.get(key);
if (coll == null) {
if (inheritance) {
InheritanceComparator comp = new InheritanceComparator();
comp.setBase(key);
coll = new TreeSet>(comp);
} else
coll = new LinkedList<>();
map.put(key, coll);
}
coll.add(value);
}
/**
* Puts the meta class corresponding to the given entity class.
*/
public void setMetaModel(Class> m2) {
Class> cls = _factory.getManagedClass(m2);
if (cls != null)
_metamodel.put(cls, m2);
}
/**
* Puts the meta class corresponding to the given persistent class.
*/
public void setMetaModel(ClassMetaData meta, Class> m2) {
_metamodel.put(meta.getDescribedType(), m2);
}
/**
* Gets the meta class corresponding to the given persistent class.
*/
public Class> getMetaModel(ClassMetaData meta, boolean load) {
return getMetaModel(meta.getDescribedType(), load);
}
/**
* Gets the meta class corresponding to the given class. If load is false, returns the meta
* class if has been set for the given persistent class earlier. If the load is true then also
* attempts to apply the current naming policy to derive meta class name and attempts to load
* the meta class.
*/
public Class> getMetaModel(Class> entity, boolean load) {
if (_metamodel.containsKey(entity))
return _metamodel.get(entity);
String m2 = _factory.getMetaModelClassName(entity.getName());
try {
ClassLoader loader = AccessController.doPrivileged(J2DoPrivHelper.getClassLoaderAction(entity));
Class> m2cls = AccessController.doPrivileged(J2DoPrivHelper.getForNameAction(m2, true, loader));
_metamodel.put(entity, m2cls);
return m2cls;
} catch (Throwable t) {
if (_log.isTraceEnabled())
_log.warn(_loc.get("meta-no-model", m2, entity, t));
}
return null;
}
// /////////////////////////////
// Configurable implementation
// /////////////////////////////
@Override
public void setConfiguration(Configuration conf) {
_conf = (OpenJPAConfiguration) conf;
_log = _conf.getLog(OpenJPAConfiguration.LOG_METADATA);
_filterRegisteredClasses = _conf.getCompatibilityInstance().getFilterPCRegistryClasses();
_typesWithoutEnhancement = _conf.getTypesWithoutEnhancement();
if (_typesWithoutEnhancement == null || _typesWithoutEnhancement.isEmpty()) {
_typesWithoutEnhancement = null;
}
}
@Override
public void startConfiguration() {
}
@Override
public void endConfiguration() {
initializeMetaDataFactory();
if (_implGen == null)
_implGen = new InterfaceImplGenerator(this);
if (_preload) {
_oids = new HashMap<>();
_impls = new HashMap<>();
_ifaces = new HashMap<>();
_aliases = new HashMap<>();
_pawares = new HashMap<>();
_nonMapped = new HashMap<>();
_subs = new HashMap<>();
// Wait till we're done loading MetaData to flip _lock boolean.
}
}
private void initializeMetaDataFactory() {
if (_factory == null) {
MetaDataFactory mdf = _conf.newMetaDataFactoryInstance();
if (mdf == null)
throw new MetaDataException(_loc.get("no-metadatafactory"));
setMetaDataFactory(mdf);
}
}
// ////////////////
// Query metadata
// ////////////////
/**
* Return query metadata for the given class, name, and classloader.
*/
public QueryMetaData getQueryMetaData(Class> cls, String name, ClassLoader envLoader, boolean mustExist) {
if (_locking) {
synchronized (this) {
return getQueryMetaDataInternal(cls, name, envLoader, mustExist);
}
} else {
return getQueryMetaDataInternal(cls, name, envLoader, mustExist);
}
}
private QueryMetaData getQueryMetaDataInternal(Class> cls, String name, ClassLoader envLoader,
boolean mustExist) {
QueryMetaData meta = getQueryMetaDataInternal(cls, name, envLoader);
if (meta == null) {
// load all the metadatas for all the known classes so that
// query names are seen and registered
resolveAll(envLoader);
meta = getQueryMetaDataInternal(cls, name, envLoader);
}
if (meta == null && mustExist) {
if (cls == null) {
throw new MetaDataException(_loc.get("no-named-query-null-class", getPersistentTypeNames(false,
envLoader), name));
} else {
throw new MetaDataException(_loc.get("no-named-query", cls, name));
}
}
return meta;
}
/**
* Resolve all known metadata classes.
*/
private void resolveAll(ClassLoader envLoader) {
Collection> types = loadPersistentTypes(false, envLoader);
for (Class> c : types) {
getMetaData(c, envLoader, false);
}
}
/**
* Return query metadata for the given class, name, and classloader.
*/
private QueryMetaData getQueryMetaDataInternal(Class> cls, String name, ClassLoader envLoader) {
if (name == null)
return null;
// check cache
QueryMetaData qm = _queries.get(name);
if (qm != null)
return qm;
// see if factory can figure out a scope for this query
if (cls == null)
cls = _factory.getQueryScope(name, envLoader);
// get metadata for class, which will find queries in metadata file
if (cls != null && getMetaData(cls, envLoader, false) != null) {
qm = _queries.get(name);
if (qm != null)
return qm;
}
if ((_sourceMode & MODE_QUERY) == 0)
return null;
// not in cache; load
_factory.load(cls, MODE_QUERY , envLoader);
return _queries.get(name);
}
/**
* Return the cached query metadata.
*/
public QueryMetaData[] getQueryMetaDatas() {
if (_locking) {
synchronized (this) {
return _queries.values().toArray(new QueryMetaData[_queries.size()]);
}
} else {
return _queries.values().toArray(new QueryMetaData[_queries.size()]);
}
}
public QueryMetaData getCachedQueryMetaData(Class> cls, String name) {
return getCachedQueryMetaData(name);
}
/**
* Return the cached query metadata for the given name.
*/
public QueryMetaData getCachedQueryMetaData(String name) {
if (_locking) {
synchronized (this) {
return _queries.get(name);
}
} else {
return _queries.get(name);
}
}
/**
* Add a new query metadata to the repository and return it.
*/
public QueryMetaData addQueryMetaData(Class> cls, String name) {
if (_locking) {
synchronized (this) {
QueryMetaData meta = newQueryMetaData(cls, name);
_queries.put(name, meta);
return meta;
}
}else{
QueryMetaData meta = newQueryMetaData(cls, name);
_queries.put(name, meta);
return meta;
}
}
public QueryMetaData addQueryMetaData(QueryMetaData meta) {
if (_locking) {
synchronized (this) {
final QueryMetaData queryMetaData = _queries.get(meta.getName());
return queryMetaData != null ? queryMetaData : _queries.put(meta.getName(), meta);
}
} else {
final QueryMetaData queryMetaData = _queries.get(meta.getName());
return queryMetaData != null ? queryMetaData : _queries.put(meta.getName(), meta);
}
}
/**
* Create a new query metadata instance.
*/
public QueryMetaData newQueryMetaData(Class> cls, String name) {
QueryMetaData meta =
new QueryMetaData(name, _conf.getCompatibilityInstance().getConvertPositionalParametersToNamed());
meta.setDefiningType(cls);
return meta;
}
/**
* Remove the given query metadata from the repository.
*/
public boolean removeQueryMetaData(QueryMetaData meta) {
if (meta == null)
return false;
if (_locking) {
synchronized (this) {
return _queries.remove(meta.getName()) != null;
}
} else {
return _queries.remove(meta.getName()) != null;
}
}
/**
* Remove query metadata for the given class name if in the repository.
*/
public boolean removeQueryMetaData(Class> cls, String name) {
if (_locking) {
synchronized (this) {
if (name == null)
return false;
return _queries.remove(name) != null;
}
} else {
if (name == null)
return false;
return _queries.remove(name) != null;
}
}
/**
* Searches all cached query metadata by name.
*/
public QueryMetaData searchQueryMetaDataByName(String name) {
return _queries.get(name);
}
/**
* Return a unique key for a given class / name. The class argument can be null.
*/
protected static Object getQueryKey(Class> cls, String name) {
if (cls == null)
return name;
QueryKey key = new QueryKey();
key.clsName = cls.getName();
key.name = name;
return key;
}
// ///////////////////
// Sequence metadata
// ///////////////////
/**
* Return sequence metadata for the given name and classloader.
*/
public SequenceMetaData getSequenceMetaData(String name, ClassLoader envLoader, boolean mustExist) {
if (_locking) {
synchronized (this) {
return getSequenceMetaDataInternal(name, envLoader, mustExist);
}
} else {
return getSequenceMetaDataInternal(name, envLoader, mustExist);
}
}
private SequenceMetaData getSequenceMetaDataInternal(String name, ClassLoader envLoader, boolean mustExist) {
SequenceMetaData meta = getSequenceMetaDataInternal(name, envLoader);
if (meta == null && SequenceMetaData.NAME_SYSTEM.equals(name)) {
if (_sysSeq == null)
_sysSeq = newSequenceMetaData(name);
return _sysSeq;
}
if (meta == null && mustExist)
throw new MetaDataException(_loc.get("no-named-sequence", name));
return meta;
}
/**
* Used internally by metadata to retrieve sequence metadatas based on possibly-unqualified
* sequence name.
*/
SequenceMetaData getSequenceMetaData(ClassMetaData context, String name, boolean mustExist) {
// try with given name
MetaDataException e = null;
try {
SequenceMetaData seq = getSequenceMetaData(name, context.getEnvClassLoader(), mustExist);
if (seq != null)
return seq;
} catch (MetaDataException mde) {
e = mde;
}
// if given name already fully qualified, give up
if (name.indexOf('.') != -1) {
if (e != null)
throw e;
return null;
}
// try with qualified name
name = ClassUtil.getPackageName(context.getDescribedType()) + "." + name;
try {
return getSequenceMetaData(name, context.getEnvClassLoader(), mustExist);
} catch (MetaDataException mde) {
// throw original exception
if (e != null)
throw e;
throw mde;
}
}
/**
* Return sequence metadata for the given name and classloader.
*/
private SequenceMetaData getSequenceMetaDataInternal(String name, ClassLoader envLoader) {
if (name == null)
return null;
// check cache
SequenceMetaData meta = _seqs.get(name);
if (meta == null) {
// load metadata for registered classes to hopefully find sequence
// definition
loadRegisteredClassMetaData(envLoader);
meta = _seqs.get(name);
}
return meta;
}
/**
* Return the cached sequence metadata.
*/
public SequenceMetaData[] getSequenceMetaDatas() {
if (_locking) {
synchronized (this) {
return _seqs.values().toArray(new SequenceMetaData[_seqs.size()]);
}
} else {
return _seqs.values().toArray(new SequenceMetaData[_seqs.size()]);
}
}
/**
* Return the cached a sequence metadata for the given name.
*/
public SequenceMetaData getCachedSequenceMetaData(String name) {
if (_locking) {
synchronized (this) {
return _seqs.get(name);
}
} else {
return _seqs.get(name);
}
}
/**
* Add a new sequence metadata to the repository and return it.
*/
public SequenceMetaData addSequenceMetaData(String name) {
if (_locking) {
synchronized (this) {
SequenceMetaData meta = newSequenceMetaData(name);
_seqs.put(name, meta);
return meta;
}
} else {
SequenceMetaData meta = newSequenceMetaData(name);
_seqs.put(name, meta);
return meta;
}
}
/**
* Create a new sequence metadata instance.
*/
protected SequenceMetaData newSequenceMetaData(String name) {
return new SequenceMetaData(name, this);
}
/**
* Remove the given sequence metadata from the repository.
*/
public boolean removeSequenceMetaData(SequenceMetaData meta) {
if (meta == null)
return false;
if (_locking) {
synchronized (this) {
return _seqs.remove(meta.getName()) != null;
}
} else {
return _seqs.remove(meta.getName()) != null;
}
}
/**
* Remove sequence metadata for the name if in the repository.
*/
public boolean removeSequenceMetaData(String name) {
if (name == null)
return false;
if (_locking) {
synchronized (this) {
return _seqs.remove(name) != null;
}
} else {
return _seqs.remove(name) != null;
}
}
/**
* Whether any system (default) listeners have been registered. Used as a quick test to
* determine whether the callback/listener mechanism has been enabled.
* @return boolean
*/
public boolean is_systemListenersActivated() {
return _systemListenersActivated;
}
/**
* Add the given system lifecycle listener.
*/
public void addSystemListener(Object listener) {
if (_locking) {
synchronized (this) {
// copy to avoid issues with ListenerList and avoid unncessary
// locking on the list during runtime
LifecycleEventManager.ListenerList listeners = new LifecycleEventManager.ListenerList(_listeners);
listeners.add(listener);
_listeners = listeners;
_systemListenersActivated = true;
}
} else {
LifecycleEventManager.ListenerList listeners = new LifecycleEventManager.ListenerList(_listeners);
listeners.add(listener);
_listeners = listeners;
_systemListenersActivated = true;
}
}
/**
* Remove the given system lifecycle listener.
*/
public boolean removeSystemListener(Object listener) {
if (_locking) {
synchronized (this) {
return removeSystemListenerInternal(listener);
}
} else {
return removeSystemListenerInternal(listener);
}
}
private boolean removeSystemListenerInternal(Object listener) {
if (!_listeners.contains(listener))
return false;
// copy to avoid issues with ListenerList and avoid unncessary
// locking on the list during runtime
LifecycleEventManager.ListenerList listeners = new LifecycleEventManager.ListenerList(_listeners);
listeners.remove(listener);
_listeners = listeners;
return true;
}
/**
* Return the system lifecycle listeners
*/
public LifecycleEventManager.ListenerList getSystemListeners() {
return _listeners;
}
/**
* Free the resources used by this repository. Closes all user sequences.
*/
@Override
public void close() {
if (_locking) {
synchronized (this) {
closeInternal();
}
} else {
closeInternal();
}
}
private void closeInternal() {
SequenceMetaData[] smds = getSequenceMetaDatas();
for (SequenceMetaData smd : smds) {
smd.close();
}
clear();
}
/**
* Query key struct.
*/
private static class QueryKey implements Serializable {
private static final long serialVersionUID = 1L;
public String clsName;
public String name;
@Override
public int hashCode() {
int clsHash = (clsName == null) ? 0 : clsName.hashCode();
int nameHash = (name == null) ? 0 : name.hashCode();
return clsHash + nameHash;
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (!(obj instanceof QueryKey))
return false;
QueryKey qk = (QueryKey) obj;
return Objects.equals(clsName, qk.clsName) && Objects.equals(name, qk.name);
}
}
/**
* Return XML metadata for a given field metadata
*
* @return XML metadata
*/
public XMLMetaData getXMLMetaData(Class> cls) {
if (_locking) {
synchronized (this) {
return getXMLMetaDataInternal(cls);
}
} else {
return getXMLMetaDataInternal(cls);
}
}
private XMLMetaData getXMLMetaDataInternal(Class> cls) {
if (cls == null) {
return null;
}
// check if cached before
XMLMetaData xmlmeta = _xmlmetas.get(cls);
if (xmlmeta != null)
return xmlmeta;
// load JAXB XML metadata
_factory.loadXMLMetaData(cls);
xmlmeta = _xmlmetas.get(cls);
return xmlmeta;
}
/**
* Create a new metadata, populate it with default information, add it to the repository, and
* return it.
*
* @param type the access type to use in populating metadata
*/
public XMLClassMetaData addXMLClassMetaData(Class> type) {
XMLClassMetaData meta = newXMLClassMetaData(type);
if(_locking){
synchronized(this){
_xmlmetas.put(type, meta);
}
}else{
_xmlmetas.put(type, meta);
}
return meta;
}
/**
* Return the cached XMLClassMetaData for the given class Return null if none.
*/
public XMLMetaData getCachedXMLMetaData(Class> cls) {
return _xmlmetas.get(cls);
}
/**
* Create a new xml class metadata
*
* @return a XMLClassMetaData
*/
protected XMLClassMetaData newXMLClassMetaData(Class> type) {
return new XMLClassMetaData(type);
}
/**
* Create a new xml field meta, add it to the fieldMap in the given xml class metadata
*
* @return a XMLFieldMetaData
*/
public XMLFieldMetaData newXMLFieldMetaData(Class> type, String name) {
return new XMLFieldMetaData(type, name);
}
public static boolean needsPreload(OpenJPAConfiguration conf) {
if (conf == null)
return false;
Options o = Configurations.parseProperties(Configurations.getProperties(conf.getMetaDataRepository()));
if (o.getBooleanProperty(PRELOAD_STR) || o.getBooleanProperty(PRELOAD_STR.toLowerCase())) {
return true;
}
return false;
}
/**
* This private worker ensures that a message is logged when an Entity is enhanced by a version of the enhancer that
* is older than the current version.
*/
private void checkEnhancementLevel(Class> cls) {
if (!_logEnhancementLevel) {
return;
}
Log log = _conf.getLog(OpenJPAConfiguration.LOG_RUNTIME);
boolean res = PCEnhancer.checkEnhancementLevel(cls, _conf.getLog(OpenJPAConfiguration.LOG_RUNTIME));
if (!log.isTraceEnabled() && res) {
// Since trace isn't enabled flip the flag so we only log this once.
_logEnhancementLevel = false;
log.info(_loc.get("down-level-entity"));
}
}
/**
* This method returns the ClassMetaData whose described type name matches the typeName parameter. It ONLY operates
* against MetaData that is currently known by this repository. Note: This method call WILL NOT resolve any
* metadata.
*/
public ClassMetaData getCachedMetaData(String typeName) {
ClassMetaData cmd = _metaStringMap.get(typeName);
if (cmd == null) {
for (ClassMetaData c : getMetaDatas()) {
if (c.getDescribedType().getName().equals(typeName)) {
_metaStringMap.put(typeName, c);
return c;
}
}
}
return cmd;
}
}