| /* |
| * 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 |
| 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<?>) { |
| 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.toString()); |
| } |
| 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 |
| @SuppressWarnings("unchecked") |
| public Object _persistence_getId() { |
| return this.primaryKey; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.persistence.internal.descriptors.PersistenceEntity#_persistence_setId(java.lang.Object) |
| */ |
| @Override |
| @SuppressWarnings("unchecked") |
| 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(); |
| } |
| } |