/*******************************************************************************
* Copyright (c) 1998, 2008 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.descriptors;
import java.util.*;
import org.eclipse.persistence.descriptors.changetracking.ObjectChangePolicy;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.sessions.AbstractSession;
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.ObjectLevelReadQuery;
/**
* <p><b>Purpose</b>: The fetch group manager controls the named fetch groups defined at
* the descriptor level. EclipseLink supports multiple, overlapped fetch groups, optionally with
* one of them as the default fetch group.
* <p>
* The domain object must implement org.eclipse.persistence.queries.FetchGroupTracker interface,
* in order to make use of the fetch group performance enhancement feature.
* <p>
* Please refer to FetchGroup class for the pros and cons of fetch group usage.
*
* @see org.eclipse.persistence.queries.FetchGroup
* @see org.eclipse.persistence.queries.FetchGroupTracker
*
* @author King Wang
* @since TopLink 10.1.3.
*/
public class FetchGroupManager implements Cloneable {
//The group map is keyed by the group name, valued by the fetch group object.
private Map fetchGroups = null;
//default fetch group
private FetchGroup defaultFetchGroup;
//ref to the descriptor
private ClassDescriptor descriptor;
/**
* Constructor
*/
public FetchGroupManager() {
}
/**
* Add a named fetch group to the descriptor
*/
public void addFetchGroup(FetchGroup group) {
//create a new fetch group and put it in the group map.
getFetchGroups().put(group.getName(), group);
}
/**
* Return the fetch group map: keyed by the group name, valued by the fetch group object.
*/
public Map getFetchGroups() {
if (fetchGroups == null) {
//lazy initialized
fetchGroups = new HashMap(2);
}
return fetchGroups;
}
/**
* Return the descriptor-level default fetch group.
* All read object and read all queries would use the default fetch group if no fetch group
* is explicitly defined for the query, unless setShouldUseDefaultFetchGroup(false); is also
* called on the query.
*
* Default fetch group should be used carefully. It would be beneficial if most of the system queries
* are for the subset of the object, so un-needed attributes data would not have to be read, and the
* users do not have to setup every query for the given fetch group, as default one is always used.
* However, if queries on object are mostly use case specific and not systematic, using default fetch group
* could cause undesirable extra round-trip and performance degradation.
*
* @see org.eclipse.persistence.queries.ObjectLevelReadQuery#setShouldUseDefaultFetchGroup(boolean)
*/
public FetchGroup getDefaultFetchGroup() {
return defaultFetchGroup;
}
/**
* Return a pre-defined named fetch group.
*/
public FetchGroup getFetchGroup(String groupName) {
return (FetchGroup)getFetchGroups().get(groupName);
}
/**
* Set the descriptor-level default fetch group.
* All read object and read all queries would use the default fetch group if no fetch group is
* explicitly defined for the query, unless setShouldUseDefaultFetchGroup(false);
* is also called on the query.
*
* Default fetch group should be used carefully. It would be beneficial if most of the system queries
* are for the subset of the object, so un-needed attributes data would not have to be read, and the
* users do not have to setup every query for the given fetch group, as default one is always used.
* However, if queries on object are mostly use case specific and not systematic, using default fetch group
* could cause undesirable extra round-trip and performance degradation.
*
* @see org.eclipse.persistence.queries.ObjectLevelReadQuery#setShouldUseDefaultFetchGroup(boolean)
*/
public void setDefaultFetchGroup(FetchGroup newDefaultFetchGroup) {
defaultFetchGroup = newDefaultFetchGroup;
}
/**
* INTERNAL:
* Return true if the object is partially fetched and cached.
* It applies to the query with fetch group.
*/
public boolean isPartialObject(Object domainObject) {
if (domainObject != null) {
FetchGroup fetchGroupInCache = ((FetchGroupTracker)domainObject)._persistence_getFetchGroup();
//if the fetch group reference is not null, it means the object is partial.
return (fetchGroupInCache != null);
}
return false;
}
/**
* INTERNAL:
* Return if the cached object data is sufficiently valid against a fetch group
*/
public boolean isObjectValidForFetchGroup(Object object, FetchGroup fetchGroup) {
FetchGroup groupInObject = ((FetchGroupTracker)object)._persistence_getFetchGroup();
return (groupInObject == null) || groupInObject.isSupersetOf(fetchGroup);
}
/**
* INTERNAL:
* Return true if the cached object data should be written in clone.
* It is used in Fetch Group case when filling in the clone from the cached object.
*/
public boolean shouldWriteInto(Object cachedObject, Object clone) {
if (isPartialObject(clone)) {
FetchGroup fetchGroupInSrc = ((FetchGroupTracker)cachedObject)._persistence_getFetchGroup();
FetchGroup fetchGroupInTarg = ((FetchGroupTracker)clone)._persistence_getFetchGroup();
//if the target fetch group is not null (i.e. fully fetched object) or if partially fetched, it's not a superset of that of the source,
//or if refresh is required, should always write (either refresh or revert) data from the cache to the clones.
return (!((fetchGroupInTarg == null) || fetchGroupInTarg.isSupersetOf(fetchGroupInSrc)) || ((FetchGroupTracker)cachedObject)._persistence_shouldRefreshFetchGroup());
}
return false;
}
/**
* INTERNAL:
* Write data of the partially fetched object into the working and backup clones
*/
public void writePartialIntoClones(Object partialObject, Object workingClone, UnitOfWorkImpl uow) {
FetchGroup fetchGroupInClone = ((FetchGroupTracker)workingClone)._persistence_getFetchGroup();
FetchGroup fetchGroupInObject = ((FetchGroupTracker)partialObject)._persistence_getFetchGroup();
Object backupClone = uow.getBackupClone(workingClone);
// Update fetch group in clone as the union of two,
// do this first to avoid fetching during method access.
FetchGroup union = unionFetchGroups(fetchGroupInObject, fetchGroupInClone);
// Finally, update clone's fetch group reference.
setObjectFetchGroup(workingClone, union, uow);
if (workingClone != backupClone) {
setObjectFetchGroup(backupClone, union, uow);
}
ObjectChangePolicy policy = descriptor.getObjectChangePolicy();
// Turn it 'off' to prevent unwanted events.
policy.dissableEventProcessing(workingClone);
try {
//if refresh is set, force to fill in fetch group data
if (((FetchGroupTracker)partialObject)._persistence_shouldRefreshFetchGroup()) {
//refresh and fill in the fetch group data
refreshFetchGroupIntoClones(partialObject, workingClone, backupClone, fetchGroupInObject, fetchGroupInClone, uow);
} else {//no refresh is enforced
//revert the unfetched attributes of the clones.
revertDataIntoUnfetchedAttributesOfClones(partialObject, workingClone, backupClone, fetchGroupInObject, fetchGroupInClone, uow);
}
} finally {
policy.enableEventProcessing(workingClone);
}
}
/**
* Refresh the fetch group data into the working and backup clones.
* This is called if refresh is enforced
*/
private void refreshFetchGroupIntoClones(Object cachedObject, Object workingClone, Object backupClone, FetchGroup fetchGroupInObject, FetchGroup fetchGroupInClone, UnitOfWorkImpl uow) {
Vector mappings = descriptor.getMappings();
boolean isObjectPartial = (fetchGroupInObject != null);
Set fetchedAttributes = isObjectPartial ? fetchGroupInObject.getAttributes() : null;
int size = mappings.size();
for (int index = 0; index < size; index++) {
DatabaseMapping mapping = (DatabaseMapping)mappings.get(index);
if ((!isObjectPartial) || ((fetchedAttributes != null) && fetchedAttributes.contains(mapping.getAttributeName()))) {
// Only refresh the fetched attributes into clones.
mapping.buildClone(cachedObject, workingClone, uow);
if (workingClone != backupClone) {
mapping.buildClone(workingClone, backupClone, uow);
}
}
}
}
/**
* Revert the clones' unfetched attributes, and leave fetched ones intact.
*/
private void revertDataIntoUnfetchedAttributesOfClones(Object cachedObject, Object workingClone, Object backupClone, FetchGroup fetchGroupInObject, FetchGroup fetchGroupInClone, UnitOfWorkImpl uow) {
Vector mappings = descriptor.getMappings();
// Fetched attributes set in working clone.
Set fetchedAttributesClone = fetchGroupInClone.getAttributes();
// Fetched attributes set in cached object.
Set fetchedAttributesCached = null;
if (fetchGroupInObject != null) {
fetchedAttributesCached = fetchGroupInObject.getAttributes();
}
int size = mappings.size();
for (int index = 0; index < size; index++) {
DatabaseMapping mapping = (DatabaseMapping)mappings.get(index);
String attributeName = mapping.getAttributeName();
// Only revert the attribute which is fetched by the cached object, but not fetched by the clone.
if (((fetchedAttributesCached == null) || fetchedAttributesCached.contains(attributeName)) && (!fetchedAttributesClone.contains(attributeName))) {
mapping.buildClone(cachedObject, workingClone, uow);
if (workingClone != backupClone) {
mapping.buildClone(workingClone, backupClone, uow);
}
}
}
}
/**
* INTERNAL:
* Copy fetch group reference from the source object to the target
*/
public void copyFetchGroupInto(Object source, Object target, AbstractSession session) {
if (isPartialObject(source)) {
setObjectFetchGroup(target, ((FetchGroupTracker)source)._persistence_getFetchGroup(), session);
}
}
/**
* INTERNAL:
* Union the fetch group of the domain object with the new fetch group.
*/
public void unionFetchGroupIntoObject(Object source, FetchGroup newFetchGroup, AbstractSession session) {
setObjectFetchGroup(source, unionFetchGroups(((FetchGroupTracker)source)._persistence_getFetchGroup(), newFetchGroup), session);
}
/**
* INTERNAL:
* Union two fetch groups.
*/
public FetchGroup unionFetchGroups(FetchGroup first, FetchGroup second) {
if ((first == null) || (second == null)) {
return null;
}
//return the superset if applied
if (first.isSupersetOf(second)) {
return first;
} else if (second.isSupersetOf(first)) {
return second;
}
//otherwise, union two fetch groups
StringBuffer unionGroupName = new StringBuffer(first.getName());
unionGroupName.append("_");
unionGroupName.append(second.getName());
FetchGroup unionFetchGroup = new FetchGroup(unionGroupName.toString());
unionFetchGroup.addAttributes(first.getAttributes());
unionFetchGroup.addAttributes(second.getAttributes());
return unionFetchGroup;
}
/**
* INTERNAL:
* Reset object attributes to the default values.
*/
public void reset(Object source) {
((FetchGroupTracker)source)._persistence_resetFetchGroup();
}
/**
* INTERNAL:
* Reset object attributes to the default their values.
*/
public void setObjectFetchGroup(Object source, FetchGroup fetchGroup, AbstractSession session) {
FetchGroupTracker tracker = (FetchGroupTracker)source;
tracker._persistence_setFetchGroup(fetchGroup);
tracker._persistence_setSession(session);
}
/**
* INTERNAL:
* Set if the tracked object is fetched from executing a query with or without refresh.
*/
public void setRefreshOnFetchGroupToObject(Object source, boolean shouldRefreshOnFetchgroup) {
((FetchGroupTracker)source)._persistence_setShouldRefreshFetchGroup(shouldRefreshOnFetchgroup);
}
/**
* Return true if the attribute of the object has already been fetched
*/
public boolean isAttributeFetched(Object object, String attributeName) {
FetchGroup fetchgroup = ((FetchGroupTracker)object)._persistence_getFetchGroup();
return (fetchgroup == null) || (fetchgroup.containsAttribute(attributeName));
}
/**
* PUBLIC:
* Return the referenced descriptor.
*/
public ClassDescriptor getDescriptor() {
return descriptor;
}
/**
* INTERNAL:
* Return the referenced descriptor.
*/
public ClassDescriptor getClassDescriptor() {
return getDescriptor();
}
/**
* Set the referenced descriptor.
*/
public void setDescriptor(ClassDescriptor descriptor) {
this.descriptor = descriptor;
}
/**
* INTERNAL:
* Prepare the query with the fetch group to add group attributes to the query
* for partial reading.
*/
public void prepareQueryWithFetchGroup(ObjectLevelReadQuery query) {
// Initialize query's fetch group.
query.initializeFetchGroup();
if (!query.hasFetchGroup()) {
// Simply return if fetch group is not defined.
return;
} else {
if (query.isReportQuery()) {
//fetch group does not work with report query
throw QueryException.fetchGroupNotSupportOnReportQuery();
}
if (query.hasPartialAttributeExpressions()) {
//fetch group does not work with partial attribute reading
throw QueryException.fetchGroupNotSupportOnPartialAttributeReading();
}
}
// Must ensure all primary key mapping attribute are in the fetch group.
for (Iterator iterator = getDescriptor().getObjectBuilder().getPrimaryKeyMappings().iterator(); iterator.hasNext();) {
DatabaseMapping mapping = (DatabaseMapping)iterator.next();
query.getFetchGroup().addAttribute(mapping.getAttributeName());
}
// Ensure locking mapping is in fetch group.
if (query.shouldMaintainCache() && getDescriptor().usesOptimisticLocking()) {
DatabaseField lockField = getDescriptor().getOptimisticLockingPolicy().getWriteLockField();
if (lockField != null) {
DatabaseMapping lockMapping = getDescriptor().getObjectBuilder().getMappingForField(lockField);
if (lockMapping != null) {
query.getFetchGroup().addAttribute(lockMapping.getAttributeName());
}
}
}
}
/**
* INTERNAL:
* Initialize the fetch groups.
*/
public void initialize(AbstractSession session) throws DescriptorException {
if (!(Helper.classImplementsInterface(getDescriptor().getJavaClass(), ClassConstants.FetchGroupTracker_class))) {
//to use fetch group, the domain class must implement FetchGroupTracker interface
session.getIntegrityChecker().handleError(DescriptorException.needToImplementFetchGroupTracker(getDescriptor().getJavaClass(), getDescriptor()));
}
if (getDefaultFetchGroup() == null) {
// Check for lazy mappings to build default fetch group.
FetchGroup fetchGroup = new FetchGroup();
boolean hasLazy = false;
for (DatabaseMapping mapping : getDescriptor().getMappings()) {
if (mapping.isForeignReferenceMapping() || (!mapping.isLazy())) {
fetchGroup.addAttribute(mapping.getAttributeName());
} else {
hasLazy = true;
}
}
if (hasLazy) {
setDefaultFetchGroup(fetchGroup);
}
}
}
/**
* INTERNAL:
* Clone the fetch group manager.
*/
public Object clone() {
try {
return super.clone();
} catch (Exception exception) {
throw new InternalError(exception.toString());
}
}
}