blob: 0116b54d5e0c434c73c7772068c43f3b1ab6320c [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// dclarke, mnorman - Dynamic Persistence
// http://wiki.eclipse.org/EclipseLink/Development/Dynamic
// (https://bugs.eclipse.org/bugs/show_bug.cgi?id=200045)
//
package org.eclipse.persistence.internal.dynamic;
//javase imports
import static org.eclipse.persistence.internal.helper.Helper.getShortClassName;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.changetracking.ChangeTracker;
import org.eclipse.persistence.dynamic.DynamicEntity;
import org.eclipse.persistence.dynamic.DynamicType;
import org.eclipse.persistence.exceptions.DynamicException;
import org.eclipse.persistence.indirection.IndirectContainer;
import org.eclipse.persistence.indirection.ValueHolderInterface;
import org.eclipse.persistence.internal.descriptors.DescriptorIterator;
import org.eclipse.persistence.internal.descriptors.PersistenceEntity;
import org.eclipse.persistence.internal.identitymaps.CacheKey;
import org.eclipse.persistence.internal.queries.JoinedAttributeManager;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.ChangeRecord;
import org.eclipse.persistence.internal.sessions.MergeManager;
import org.eclipse.persistence.internal.sessions.ObjectChangeSet;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.queries.FetchGroup;
import org.eclipse.persistence.queries.FetchGroupTracker;
import org.eclipse.persistence.queries.ObjectBuildingQuery;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.sessions.remote.DistributedSession;
/**
* This abstract class is used to represent an entity which typically is not
* realized in Java code. In combination with the DynamicClassLoader ASM is used
* to generate subclasses that will work within EclipseLink's framework. Since
* no concrete fields or methods exist on this class the mappings used must be
* customized to use a custom AttributeAccessor ({@link ValuesAccessor}).
* <p>
* <b>Type/Property Meta-model</b>: This dynamic entity approach also includes a
* meta-model facade to simplify access to the types and property information so
* that clients can more easily understand the model. Each
* {@link DynamicTypeImpl} wraps the underlying EclipseLink
* relational-descriptor and the {@link DynamicPropertiesManager} wraps each mapping.
* The client application can use these types and properties to facilitate
* generic access to the entity instances and are required for creating new
* instances as well as for accessing the Java class needed for JPA and
* EclipseLink native API calls.
*
* @author dclarke, mnorman
* @since EclipseLink 1.2
*/
public abstract class DynamicEntityImpl implements DynamicEntity, PersistenceEntity,
ChangeTracker, FetchGroupTracker {
/**
* Fetch properties manager.
*
* @return the dynamic properties manager
*/
public abstract DynamicPropertiesManager fetchPropertiesManager();
protected Map<String, PropertyWrapper> propertiesMap = new HashMap<>();
/**
* Instantiates a new dynamic entity impl.
*/
protected DynamicEntityImpl() {
postConstruct(); // life-cycle callback
}
/**
* Gets the properties map.
*
* @return the properties map
*/
public Map<String, PropertyWrapper> getPropertiesMap() {
return propertiesMap;
}
/**
* Post construct.
*/
protected void postConstruct() {
DynamicPropertiesManager dpm = fetchPropertiesManager();
dpm.postConstruct(this);
}
/**
* Gets internal impl class of {@link DynamicType}.
*
* @return Dynamic type of this entity
* @throws DynamicException if type is null
*/
public DynamicTypeImpl getType() throws DynamicException {
DynamicType type = fetchPropertiesManager().getType();
if (type == null) {
throw DynamicException.entityHasNullType(this);
}
return (DynamicTypeImpl) type;
}
//DynamicEntity API
/* (non-Javadoc)
* @see org.eclipse.persistence.dynamic.DynamicEntity#get(java.lang.String)
*/
@Override
@SuppressWarnings({"unchecked"})
public <T> T get(String propertyName) throws DynamicException {
DynamicPropertiesManager dpm = fetchPropertiesManager();
if (dpm.contains(propertyName)) {
if (_persistence_getFetchGroup() != null) {
String errorMsg = _persistence_getFetchGroup().onUnfetchedAttribute(this,
propertyName);
if (errorMsg != null) {
throw DynamicException.invalidPropertyName(dpm.getType(), propertyName);
}
}
PropertyWrapper wrapper = propertiesMap.get(propertyName);
if (wrapper == null) { // properties can be added after constructor is called
wrapper = new PropertyWrapper();
propertiesMap.put(propertyName, wrapper);
}
Object value = wrapper.getValue();
// trigger any indirection
if (value instanceof ValueHolderInterface) {
value = ((ValueHolderInterface<?>) value).getValue();
}
else if (value instanceof IndirectContainer) {
value = ((IndirectContainer<?>) value).getValueHolder().getValue();
}
try {
return (T) value;
} catch (ClassCastException cce) {
ClassDescriptor descriptor = getType().getDescriptor();
DatabaseMapping dm = null;
if (descriptor != null) {
dm = descriptor.getMappingForAttributeName(propertyName);
}
else {
dm = new UnknownMapping(propertyName);
}
throw DynamicException.invalidGetPropertyType(dm, cce);
}
}
else {
throw DynamicException.invalidPropertyName(dpm.getType(), propertyName);
}
}
/* (non-Javadoc)
* @see org.eclipse.persistence.dynamic.DynamicEntity#isSet(java.lang.String)
*/
@Override
public boolean isSet(String propertyName) throws DynamicException {
if (fetchPropertiesManager().contains(propertyName)) {
if (_persistence_getFetchGroup() != null &&
!_persistence_getFetchGroup().containsAttributeInternal(propertyName)) {
return false;
}
PropertyWrapper wrapper = propertiesMap.get(propertyName);
if (wrapper == null) { // properties can be added after constructor is called
wrapper = new PropertyWrapper();
propertiesMap.put(propertyName, wrapper);
}
return wrapper.isSet();
}
else {
throw DynamicException.invalidPropertyName(fetchPropertiesManager().getType(),
propertyName);
}
}
/* (non-Javadoc)
* @see org.eclipse.persistence.dynamic.DynamicEntity#set(java.lang.String, java.lang.Object)
*/
@Override
public DynamicEntity set(String propertyName, Object value) throws DynamicException {
return set(propertyName, value, true);
}
/**
* Sets the.
*
* @param propertyName the property name
* @param value the value
* @param firePropertyChange the fire property change
* @return the dynamic entity
* @throws DynamicException the dynamic exception
*/
public DynamicEntity set(String propertyName, Object value, boolean firePropertyChange) throws DynamicException {
DynamicPropertiesManager dpm = fetchPropertiesManager();
dpm.checkSet(propertyName, value); // life-cycle callback
if (_persistence_getFetchGroup() != null) {
String errorMsg = _persistence_getFetchGroup().onUnfetchedAttributeForSet(this,
propertyName);
if (errorMsg != null) {
throw DynamicException.invalidPropertyName(dpm.getType(), propertyName);
}
}
PropertyWrapper wrapper = propertiesMap.get(propertyName);
if (wrapper == null) { // properties can be added after constructor is called
wrapper = new PropertyWrapper();
propertiesMap.put(propertyName, wrapper);
}
Object oldValue = null;
Object wrapperValue = wrapper.getValue();
if (wrapperValue instanceof ValueHolderInterface<?>) {
@SuppressWarnings({"unchecked"})
ValueHolderInterface<Object> vh = (ValueHolderInterface<Object>) wrapperValue;
if (vh.isInstantiated()) {
oldValue = vh.getValue();
}
vh.setValue(value);
wrapper.isSet(true);
}
else {
oldValue = wrapperValue;
wrapper.setValue(value);
wrapper.isSet(true);
}
if (changeListener != null && firePropertyChange) {
changeListener.propertyChange(new PropertyChangeEvent(this, propertyName,
oldValue, value));
}
return this;
}
// Made static final for performance reasons.
public static final class PropertyWrapper {
private Object value = null;
private boolean isSet = false;
/**
* Instantiates a new property wrapper.
*/
public PropertyWrapper() {
}
/**
* Instantiates a new property wrapper.
*
* @param value the value
*/
public PropertyWrapper(Object value) {
setValue(value);
}
/**
* Gets the value.
*
* @return the value
*/
public Object getValue() {
return value;
}
/**
* Sets the value.
*
* @param value the new value
*/
public void setValue(Object value) {
this.value = value;
}
/**
* Checks if is sets the.
*
* @return true, if is sets the
*/
public boolean isSet() {
return isSet;
}
/**
* Checks if is set.
*
* @param isSet the is set
*/
public void isSet(boolean isSet) {
this.isSet = isSet;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (isSet) {
sb.append("[T]");
}
else {
if (value == null) {
sb.append("[F]");
}
else {
sb.append("[d]");
}
}
if (value == null) {
sb.append("<null>");
}
else {
sb.append(value);
}
return sb.toString();
}
}
// Made static final for performance reasons.
static final class UnknownMapping extends DatabaseMapping {
/**
* Instantiates a new unknown mapping.
*
* @param propertyName the property name
*/
public UnknownMapping(String propertyName) {
setAttributeName(propertyName);
}
/* (non-Javadoc)
* @see org.eclipse.persistence.mappings.DatabaseMapping#buildBackupClone(java.lang.Object, java.lang.Object, org.eclipse.persistence.internal.sessions.UnitOfWorkImpl)
*/
@Override
public void buildBackupClone(Object clone, Object backup, UnitOfWorkImpl unitOfWork) {
}
/* (non-Javadoc)
* @see org.eclipse.persistence.mappings.DatabaseMapping#buildClone(java.lang.Object, org.eclipse.persistence.internal.identitymaps.CacheKey, java.lang.Object, java.lang.Integer, org.eclipse.persistence.internal.sessions.AbstractSession)
*/
@Override
public void buildClone(Object original, CacheKey cacheKey, Object clone, Integer refreshCascade, AbstractSession cloningSession) {
}
/* (non-Javadoc)
* @see org.eclipse.persistence.mappings.DatabaseMapping#buildCloneFromRow(org.eclipse.persistence.internal.sessions.AbstractRecord, org.eclipse.persistence.internal.queries.JoinedAttributeManager, java.lang.Object, org.eclipse.persistence.internal.identitymaps.CacheKey, org.eclipse.persistence.queries.ObjectBuildingQuery, org.eclipse.persistence.internal.sessions.UnitOfWorkImpl, org.eclipse.persistence.internal.sessions.AbstractSession)
*/
@Override
public void buildCloneFromRow(AbstractRecord databaseRow,
JoinedAttributeManager joinManager, Object clone, CacheKey sharedCacheKey, ObjectBuildingQuery sourceQuery,
UnitOfWorkImpl unitOfWork, AbstractSession executionSession) {
}
/* (non-Javadoc)
* @see org.eclipse.persistence.mappings.DatabaseMapping#cascadePerformRemoveIfRequired(java.lang.Object, org.eclipse.persistence.internal.sessions.UnitOfWorkImpl, java.util.Map)
*/
@Override
public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow,
Map visitedObjects) {
}
/* (non-Javadoc)
* @see org.eclipse.persistence.mappings.DatabaseMapping#cascadeRegisterNewIfRequired(java.lang.Object, org.eclipse.persistence.internal.sessions.UnitOfWorkImpl, java.util.Map)
*/
@Override
public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow,
Map visitedObjects) {
}
/* (non-Javadoc)
* @see org.eclipse.persistence.mappings.DatabaseMapping#compareForChange(java.lang.Object, java.lang.Object, org.eclipse.persistence.internal.sessions.ObjectChangeSet, org.eclipse.persistence.internal.sessions.AbstractSession)
*/
@Override
public ChangeRecord compareForChange(Object clone, Object backup, ObjectChangeSet owner,
AbstractSession session) {
return null;
}
/* (non-Javadoc)
* @see org.eclipse.persistence.mappings.DatabaseMapping#compareObjects(java.lang.Object, java.lang.Object, org.eclipse.persistence.internal.sessions.AbstractSession)
*/
@Override
public boolean compareObjects(Object firstObject, Object secondObject,
AbstractSession session) {
return false;
}
/* (non-Javadoc)
* @see org.eclipse.persistence.mappings.DatabaseMapping#fixObjectReferences(java.lang.Object, java.util.Map, java.util.Map, org.eclipse.persistence.queries.ObjectLevelReadQuery, org.eclipse.persistence.sessions.remote.RemoteSession)
*/
@Override
public void fixObjectReferences(Object object, Map objectDescriptors, Map processedObjects,
ObjectLevelReadQuery query, DistributedSession session) {
}
/* (non-Javadoc)
* @see org.eclipse.persistence.mappings.DatabaseMapping#iterate(org.eclipse.persistence.internal.descriptors.DescriptorIterator)
*/
@Override
public void iterate(DescriptorIterator iterator) {
}
/* (non-Javadoc)
* @see org.eclipse.persistence.mappings.DatabaseMapping#mergeChangesIntoObject(java.lang.Object, org.eclipse.persistence.internal.sessions.ChangeRecord, java.lang.Object, org.eclipse.persistence.internal.sessions.MergeManager, org.eclipse.persistence.internal.sessions.AbstractSession)
*/
@Override
public void mergeChangesIntoObject(Object target, ChangeRecord changeRecord, Object source,
MergeManager mergeManager, AbstractSession targetSession) {
}
/* (non-Javadoc)
* @see org.eclipse.persistence.mappings.DatabaseMapping#mergeIntoObject(java.lang.Object, boolean, java.lang.Object, org.eclipse.persistence.internal.sessions.MergeManager, org.eclipse.persistence.internal.sessions.AbstractSession)
*/
@Override
public void mergeIntoObject(Object target, boolean isTargetUninitialized, Object source,
MergeManager mergeManager, AbstractSession targetSession) {
}
}
//PersistenceEntity API
/**
* Cache the primary key within the entity
*
* @see PersistenceEntity#_persistence_setId(Object)
*/
private Object primaryKey;
protected CacheKey cacheKey;
/* (non-Javadoc)
* @see org.eclipse.persistence.internal.descriptors.PersistenceEntity#_persistence_getId()
*/
@Override
public Object _persistence_getId() {
return this.primaryKey;
}
/* (non-Javadoc)
* @see org.eclipse.persistence.internal.descriptors.PersistenceEntity#_persistence_setId(java.lang.Object)
*/
@Override
public void _persistence_setId(Object pk) {
this.primaryKey = pk;
}
/* (non-Javadoc)
* @see org.eclipse.persistence.internal.descriptors.PersistenceEntity#_persistence_getCacheKey()
*/
@Override
public CacheKey _persistence_getCacheKey() {
return this.cacheKey;
}
/* (non-Javadoc)
* @see org.eclipse.persistence.internal.descriptors.PersistenceEntity#_persistence_setCacheKey(org.eclipse.persistence.internal.identitymaps.CacheKey)
*/
@Override
public void _persistence_setCacheKey(CacheKey cacheKey) {
this.cacheKey = cacheKey;
}
//ChangeTracker API
/**
* ChangeListener used for attribute change tracking processed in the
* property. Set through
* {@link ChangeTracker#_persistence_setPropertyChangeListener(PropertyChangeListener)}
*/
private PropertyChangeListener changeListener = null;
/* (non-Javadoc)
* @see org.eclipse.persistence.descriptors.changetracking.ChangeTracker#_persistence_getPropertyChangeListener()
*/
@Override
public PropertyChangeListener _persistence_getPropertyChangeListener() {
return this.changeListener;
}
/* (non-Javadoc)
* @see org.eclipse.persistence.descriptors.changetracking.ChangeTracker#_persistence_setPropertyChangeListener(java.beans.PropertyChangeListener)
*/
@Override
public void _persistence_setPropertyChangeListener(PropertyChangeListener listener) {
this.changeListener = listener;
}
//FetchGroup API
/**
* FetchGroup cached by
* {@link FetchGroupTracker#_persistence_setFetchGroup(FetchGroup)}
*/
private FetchGroup fetchGroup;
/**
* {@link FetchGroupTracker#_persistence_setShouldRefreshFetchGroup(boolean)}
*/
private boolean refreshFetchGroup = false;
/* (non-Javadoc)
* @see org.eclipse.persistence.queries.FetchGroupTracker#_persistence_getFetchGroup()
*/
@Override
public FetchGroup _persistence_getFetchGroup() {
return this.fetchGroup;
}
/* (non-Javadoc)
* @see org.eclipse.persistence.queries.FetchGroupTracker#_persistence_setFetchGroup(org.eclipse.persistence.queries.FetchGroup)
*/
@Override
public void _persistence_setFetchGroup(FetchGroup group) {
this.fetchGroup = group;
}
/* (non-Javadoc)
* @see org.eclipse.persistence.queries.FetchGroupTracker#_persistence_setShouldRefreshFetchGroup(boolean)
*/
@Override
public void _persistence_setShouldRefreshFetchGroup(boolean shouldRefreshFetchGroup) {
this.refreshFetchGroup = shouldRefreshFetchGroup;
}
/* (non-Javadoc)
* @see org.eclipse.persistence.queries.FetchGroupTracker#_persistence_shouldRefreshFetchGroup()
*/
@Override
public boolean _persistence_shouldRefreshFetchGroup() {
return this.refreshFetchGroup;
}
/**
* Return true if the attribute is in the fetch group being tracked.
*
* @param attribute the attribute
* @return true, if successful
*/
@Override
public boolean _persistence_isAttributeFetched(String attribute) {
return this.fetchGroup == null || this.fetchGroup.containsAttributeInternal(attribute);
}
/**
* Reset all attributes of the tracked object to the un-fetched state with
* initial default values.
*/
@Override
public void _persistence_resetFetchGroup() {
}
/**
* Session cached by
* {@link FetchGroupTracker#_persistence_setSession(Session)}
*/
private Session session;
/* (non-Javadoc)
* @see org.eclipse.persistence.queries.FetchGroupTracker#_persistence_getSession()
*/
@Override
public Session _persistence_getSession() {
return this.session;
}
/* (non-Javadoc)
* @see org.eclipse.persistence.queries.FetchGroupTracker#_persistence_setSession(org.eclipse.persistence.sessions.Session)
*/
@Override
public void _persistence_setSession(Session session) {
this.session = session;
}
/**
* String representation of the dynamic entity using the entity type name
* and the primary key values - something like {Emp 10} or {Phone 234-5678 10}.
*
* @return the string
*/
@Override
public String toString() {
// this will print something like {Emp 10} or {Phone 234-5678 10}
StringBuilder sb = new StringBuilder(20);
sb.append('{');
sb.append(getShortClassName(this.getClass()));
if (primaryKey != null) {
sb.append(' ');
sb.append(primaryKey);
}
sb.append('}');
return sb.toString();
}
}