org.apache.myfaces.trinidad.change.SessionChangeManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of trinidad-api Show documentation
Show all versions of trinidad-api Show documentation
Public API for the Apache MyFaces Trinidad project
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.myfaces.trinidad.change;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.util.CollectionUtils;
import org.apache.myfaces.trinidad.util.ComponentUtils;
import org.w3c.dom.Document;
/**
* A ChangeManager implementation that manages persisting the added Changes at the session.
* This means the lifetime of Changes added such is within the session scope. If any of the changes
* are managed as state changes and restored by JSF state saving mechanism, the SessionChangeManager
* will not re-apply such changes. For example: AttributeComponentChanges are not applied during
* a postback unless its target component happened to be a result of any move/add operation, this is
* because attribute changes are handled by state manager during postback for common cases.
* @version $Name: $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/change/SessionChangeManager.java#0 $) $Date: 10-nov-2005.19:06:35 $
*/
public class SessionChangeManager extends BaseChangeManager
{
/**
* {@inheritDoc}
* @param context The FacesContext instance for the current request.
*/
@Override
public void applyComponentChangesForCurrentView(FacesContext context)
{
_applyComponentChanges(context, null);
}
/**
* {@inheritDoc}
* @param context The FacesContext instance for the current request.
*/
@Override
public void applyComponentChangesForSubtree(
FacesContext context,
NamingContainer root
)
{
String rootId = null;
if (root != null)
{
if (!(root instanceof UIComponent))
{
throw new IllegalArgumentException(_LOG.getMessage(
"INVALID_TYPE", root));
}
rootId = ComponentUtils.getScopedIdForComponent((UIComponent)root, context.getViewRoot());
}
_applyComponentChanges(context, rootId);
}
/**
* {@inheritDoc}
*/
@Override
public void applySimpleComponentChanges(FacesContext context, UIComponent component)
{
// we don't need to apply the component changes if we restored the state, since the
// attributes will be up-to-date
if (!_isStateRestored(context))
{
String sessionKey = _getSessionKey(context);
ChangesForView changesForView = _getChangesForView(context, sessionKey, false);
if (changesForView != null)
{
changesForView.applySimpleComponentChanges(context, component);
}
}
}
/**
* @inheritDoc
*/
@Override
@SuppressWarnings("deprecation")
public void addDocumentChange(
FacesContext facesContext,
UIComponent uiComponent,
DocumentChange change)
{
addDocumentChangeWithOutcome(facesContext, uiComponent, change);
}
/**
* @inheritDoc
*/
@Override
public ChangeOutcome addDocumentChangeWithOutcome(
FacesContext facesContext,
UIComponent uiComponent,
DocumentChange change)
{
if (facesContext == null || uiComponent == null || change == null)
throw new IllegalArgumentException(_LOG.getMessage(
"CANNOT_ADD_CHANGE_WITH_FACECONTEXT_OR_UICOMPONENT_OR_NULL"));
return ChangeOutcome.CHANGE_NOT_APPLIED;
}
/**
* In this implementation, if a simple attribute document change was applied, we will remove
* previously added component change on the same component and attribute, if any.
*/
@Override
public NotificationOutcome documentChangeApplied(
FacesContext facesContext,
UIComponent component,
ComponentChange componentChange)
{
NotificationOutcome outcome = super.documentChangeApplied(facesContext,
component,
componentChange);
if (componentChange instanceof AttributeComponentChange)
{
AttributeComponentChange attributeChange = (AttributeComponentChange)componentChange;
// given that document change was applied, any previously added component change is redundant,
// remove it
ComponentChange removedChange = _removeSimpleComponentChange(facesContext,
component,
attributeChange);
if (removedChange != null)
{
if (_LOG.isFine())
{
_LOG.fine("REMOVED_DOC_CHANGE_BECAUSE_COMP_CHANGE_ADDED", new Object[] {attributeChange.getAttributeName(), component});
}
outcome = NotificationOutcome.HANDLED;
}
}
return outcome;
}
/**
* Adds a ComponentChange and registers against the supplied component.
* Changes added thus live at Session scope.
* Use applyComponentChangesForCurrentView() to apply these changes.
* @param context The FacesContext instance for the current request.
* @param targetComponent The target component against which this change needs
* to be registered and applied later on.
* @param componentChange The ComponentChange to add.
*/
protected void addComponentChangeImpl(
FacesContext context,
UIComponent targetComponent,
ComponentChange componentChange)
{
// try to collapse AttributeComponentChanges, handling component movement so that
// we can collapse any attribute change on the same component
if (componentChange instanceof AttributeComponentChange)
{
_replaceAttributeChange(context,
targetComponent,
((AttributeComponentChange)componentChange).getAttributeName(),
(AttributeComponentChange)componentChange,
false); // replace no matter what
}
else
{
String sessionKey = _getSessionKey(context);
ChangesForView changesForView = _getChangesForView(context, sessionKey, true);
// get the absolute scopedId for the target component so that we have a unique identifier
// to compare
String scopedIdForTargetComponent =
ComponentUtils.getScopedIdForComponent(targetComponent, context.getViewRoot());
String logicalScopedIdForTargetComponent =
ComponentUtils.getLogicalScopedIdForComponent(targetComponent, context.getViewRoot());
_insertComponentChange(changesForView,
scopedIdForTargetComponent,
logicalScopedIdForTargetComponent,
componentChange);
// dirty the key in the session for failover
context.getExternalContext().getSessionMap().put(sessionKey, changesForView);
}
}
/**
* @inheritDoc
*/
@Override
public AttributeComponentChange replaceAttributeChangeIfPresent(
FacesContext context,
UIComponent component,
AttributeComponentChange attributeComponentChange)
{
return _replaceAttributeChange(context,
component,
attributeComponentChange.getAttributeName(),
attributeComponentChange,
true);
}
/**
* We don't support DocumentChange persistence
*/
@Override
protected Document getDocument(FacesContext context)
{
return null;
}
/**
* Removes (if any) the previously added simple component change for the supplied component and
* attribute
*
* @return The removed ComponentChange instance, or null if the ComponentChange was not removed
*/
private ComponentChange _removeSimpleComponentChange(
FacesContext facesContext,
UIComponent component,
AttributeComponentChange componentChange)
{
String sessionKey = _getSessionKey(facesContext);
ChangesForView changesForView = _getChangesForView(facesContext, sessionKey, true);
String logicalScopedId =
ComponentUtils.getLogicalScopedIdForComponent(component, facesContext.getViewRoot());
// first remove from the simple component changes structure that we maintained for convenience
changesForView.removeAttributeChange(logicalScopedId, componentChange);
// next, remove (replace with null) any attribute change for this attribute from the global
// changes list, handling the case where the component could have been moved after the
// attribute change was added
return _replaceAttributeChange(facesContext,
component,
componentChange.getAttributeName(),
null,
true);
}
/**
* @param context
* @param component
* @param attributeName The name of the attribute for which the attribute change is to be replaced
* @param attributeComponentChange The attributeChange that should now replace, If null, we just remove the old one
* and return
* @param onlyIfPresent If true, we only insert a new changed if we removed an old one
* @return the removed AttributeComponentChange, if any
*/
private AttributeComponentChange _replaceAttributeChange(
FacesContext context,
UIComponent component,
String attributeName,
AttributeComponentChange attributeComponentChange,
boolean onlyIfPresent)
{
// get the absolute scopedId for the target component so that we have a unique identifier
// to compare
String scopedIdForTargetComponent = ComponentUtils.getScopedIdForComponent(
component,
context.getViewRoot());
// check if we have an existing attribute change for the same attribute name,
// if found, remove it
String sessionKey = _getSessionKey(context);
ChangesForView changesForView = _getChangesForView(context, sessionKey, true);
// remove the attribute change first if it existed
AttributeComponentChange replaced = _extractAttributeChange(changesForView,
scopedIdForTargetComponent,
attributeName);
// if found, we insert the change instance we are asked to replace with
if ( attributeComponentChange != null && (!onlyIfPresent || (replaced != null)) )
{
// a sanity check - the change better be for the same attribute
if ( !(attributeName.equals(attributeComponentChange.getAttributeName())) )
{
throw new IllegalArgumentException("The supplied attribute name does not match the " +
"attribute name in the supplied attribute change");
}
String logicalScopedIdForTargetComponent = ComponentUtils.getLogicalScopedIdForComponent(
component,
context.getViewRoot());
_insertComponentChange(changesForView,
scopedIdForTargetComponent,
logicalScopedIdForTargetComponent,
attributeComponentChange);
// dirty the key in the session for failover
context.getExternalContext().getSessionMap().put(sessionKey, changesForView);
}
return replaced;
}
/**
* Check if we have an existing attribute change for the same attribute name:
* - if not found, return null
* - if found, remove and return the old change instance
*
* Note that this function will handle removing a component's attribute change even if the
* component was moved between naming containers after the attribute change was added
*
* @param changesForView
* @param scopedIdForTargetComponent
* @param attributeName The name of attribute for which the attribute change is to be extracted
* @return the old change instance, null if not found
*/
private AttributeComponentChange _extractAttributeChange(
ChangesForView changesForView,
String scopedIdForTargetComponent,
String attributeName)
{
AttributeComponentChange extracted = null;
// would really rather use a Deque here and iterate backwards, which would also make
// handling the rename changes easier
Iterator changes =
changesForView.getComponentChangesForView().iterator();
// list of changes that have renamed the scoped id of this component. We need to
// handle this aliasing when traversing through the changes looking for matches
Iterator renameChanges =
changesForView.getRenameChanges(scopedIdForTargetComponent);
// we need to look through the rename list to map from the current names to
// the new names
MoveChildComponentChange nextRenameChange;
String currTargetScopedId;
if (renameChanges.hasNext())
{
// we have at least one rename change, so get it and find the name that this
// component was originally known by
nextRenameChange = renameChanges.next();
currTargetScopedId = nextRenameChange.getSourceScopedId();
}
else
{
nextRenameChange = null;
currTargetScopedId = scopedIdForTargetComponent;
}
// loop forward through the changes looking for AttributeChanges to collapse
while (changes.hasNext())
{
QualifiedComponentChange currQualifiedChange = changes.next();
if (currQualifiedChange.getComponentChange() == nextRenameChange)
{
// we got a match, so update the scoped id we should be looking for
currTargetScopedId = nextRenameChange.getDestinationScopedId();
nextRenameChange = (renameChanges.hasNext())
? renameChanges.next()
: null;
}
else if (currQualifiedChange.getTargetComponentScopedId().equals(currTargetScopedId))
{
// found a change on the same component. Check if it's an AttributeChange
ComponentChange currChange = currQualifiedChange.getComponentChange();
if (currChange instanceof AttributeComponentChange)
{
AttributeComponentChange currAttributeChange = (AttributeComponentChange)currChange;
// Check if the AttributeChange is for the same attribute
if (attributeName.equals(currAttributeChange.getAttributeName()))
{
// the old AttributeChange is for the same attribute, so remove it since the
// new AttributeChange would eclipse it anyway.
changes.remove();
extracted = currAttributeChange;
break;
}
}
}
}
return extracted;
}
/**
* insert a component change for a specific component
*
* @param changesForView
* @param scopedIdForTargetComponent
* @param logicalScopedIdForTargetComponent
* @param componentChange
*/
private void _insertComponentChange(ChangesForView changesForView,
String scopedIdForTargetComponent,
String logicalScopedIdForTargetComponent,
ComponentChange componentChange)
{
QualifiedComponentChange newQualifiedChange =
new QualifiedComponentChange(scopedIdForTargetComponent,
logicalScopedIdForTargetComponent,
componentChange);
changesForView.addChange(newQualifiedChange);
}
/**
* Implementation shared by applyComponentChangesForCurrentView() and
* applyComponentChangesForSubtree().
* @param context The FacesContext instance for this request.
* @param rootId The scoped id of theNamingContainer that contains the
* component subtree to which ComponentChanges should be applied. If null,
* all changes are applied.
*/
private void _applyComponentChanges(
FacesContext context,
String rootId
)
{
ChangesForView changesForView = _getReadOnlyChangesForView(context);
UIViewRoot viewRoot = context.getViewRoot();
// retrieve the ComponentChanges for this current viewid
boolean isStateRestored = _isStateRestored(context);
// components that have their attribute application forced because a change that would confuse
// state saving has been applied
Set attributeForcedComponents = new HashSet();
// loop through the viewId's changes, applying the changes
for (QualifiedComponentChange qualifiedChange : changesForView.getComponentChangesForView())
{
ComponentChange componentChange = qualifiedChange.getComponentChange();
String targetComponentScopedId = qualifiedChange.getTargetComponentScopedId();
// We do not apply attribute changes if it is a postback, because we expect that
// 1. Users calling ChangeManager.addComponentChange() would also apply the change right at
// that point in time (maybe by calling ComponentChange.changeComponent() method).
// 2. If #1 was done, JSF state manager will consider this a state change and will store and
// restore it during subsequent postbacks, so there is no need for applying attribute changes
// for postback cases. There are few exceptions where the state management will not help, for
// which we force the attribute changes even when it is a postback.
if (isStateRestored &&
componentChange instanceof AttributeComponentChange &&
!attributeForcedComponents.contains(targetComponentScopedId))
{
continue;
}
// If change target for the qualified change is not inside of the specified root, skip
if (!_acceptChange(qualifiedChange, rootId))
continue;
UIComponent targetComponent = viewRoot.findComponent(targetComponentScopedId);
// Possible that the target component no more exists in the view, if yes, skip
if (targetComponent == null)
{
_LOG.info(this.getClass().getName(),
"applyComponentChangesForCurrentView",
"TARGET_COMPONENT_MISSING_CHANGE_FAILED",
targetComponentScopedId);
continue;
}
// Apply the change
componentChange.changeComponent(targetComponent);
// Now that the change is applied, we can identify if the components altered by the currently
// applied change needs forced application of any further changes regardless of request
// being a postback.
if (componentChange instanceof MoveChildComponentChange)
{
String destinationScopedId =
((MoveChildComponentChange)componentChange).getDestinationScopedId();
// we no longer need to force the old scoped id, if any, but we do need to force the new one
attributeForcedComponents.remove(targetComponentScopedId);
attributeForcedComponents.add(destinationScopedId);
}
else if (componentChange instanceof SetFacetChildComponentChange)
{
String facetName = ((SetFacetChildComponentChange)componentChange).getFacetName();
UIComponent facetComponent = targetComponent.getFacet(facetName);
if (facetComponent != null)
{
String facetScopedId = ComponentUtils.getScopedIdForComponent(facetComponent, viewRoot);
attributeForcedComponents.add(facetScopedId);
}
}
else if (componentChange instanceof AddChildComponentChange)
{
// Get the added component from AddComponentChange, this component is actually re-created
// from the proxy, and not the actual added component.
// Bit hacky but this is only way to get Id.
String addedComponentId = ((AddChildComponentChange)componentChange).getComponent().getId();
// Now get the actual added component finding from the parent to which it was added to
UIComponent addedComponent = ComponentUtils.findRelativeComponent(targetComponent,
addedComponentId);
if (addedComponent != null)
{
String addedChildComponentScopedId= ComponentUtils.getScopedIdForComponent(addedComponent,
viewRoot);
attributeForcedComponents.add(addedChildComponentScopedId);
}
}
}
}
/**
* Is the state restored by JSF state manager in this request. This is usually true if this is a
* postback request. Additionally check if the document tag created a document component, because
* if this is the case, we are sure that there was no state restoration.
*/
private boolean _isStateRestored(FacesContext facesContext)
{
/*
* We will always return false for now. The reason is, if the page has a included fragment,
* and the fragment gets replaced during ppr, the changes inside the region will be lost.
*/
return false;
//boolean docCompCreated = Boolean.TRUE.equals(facesContext.getExternalContext().
// getRequestMap().get(UIXComponentELTag.DOCUMENT_CREATED_KEY));
//return (docCompCreated) ? false : RequestContext.getCurrentInstance().isPostback();
}
/**
* Tests whether the specified change should be applied based on the
* specified root id. If root id is null, all changes are accepted/applied.
* If the root id is non-null, only changes which target ids underneath
* the root id are accepted/applied.
*
* @param qualifiedChange the change to test
* @param rootId the scoped id of the NamingContainer for which we
* are applying changes
* @return true if rootId is null, or if the qualifiedChange targets a
* component underneath the NamingContainer identified by the rootId.
*/
private boolean _acceptChange(
QualifiedComponentChange qualifiedChange,
String rootId
)
{
if (rootId != null)
{
String id = qualifiedChange.getTargetComponentScopedId();
return (id.startsWith(rootId) && (id.length() != rootId.length()));
}
else
{
return true;
}
}
private ChangesForView _getReadOnlyChangesForView(FacesContext context)
{
String sessionKey = _getSessionKey(context);
return _getChangesForView(context, sessionKey, false);
}
/**
* Gets the in-order list of component changes for the given view.
* @param context The FacesContext instance for this request.
* @param sessionKey The composite session key based on the viewId
* @param createIfNecessary Indicates whether the underlying datastructures
* that store the component changes needs to be created if absent.
* @return The ChangesForView object containing information about the changes for the specified
* viewId, including in-order list of component changes for the supplied view. This
* will be in the same order in which the component changes were added through
* calls to addComponentChange()
.
*/
private ChangesForView _getChangesForView(
FacesContext context,
String sessionKey,
boolean createIfNecessary)
{
ExternalContext extContext = context.getExternalContext();
Map sessionMap = extContext.getSessionMap();
Object changes = sessionMap.get(sessionKey);
if (changes == null)
{
if (createIfNecessary)
{
// make sure we only create this viewId's changes entry once
Object session = extContext.getSession(true);
synchronized (session)
{
changes = sessionMap.get(sessionKey);
if (changes == null)
{
ChangesForView changesForView = new ChangesForView(true);
sessionMap.put(sessionKey, changesForView);
return changesForView; // return the newly created changes
}
else
{
return (ChangesForView)changes; // another thread created the changes for us
}
}
}
else
{
return _EMPTY_CHANGES; // no changes and we aren't allowed to create them
}
}
else
{
return (ChangesForView)changes; // we have the changes
}
}
/**
* Return the Session key to store the changes for this viewId in. We store each viewId under
* a different key to avoid needing to failover all of the changes whenever the changes for
* a particular viewId are modified.
* @param context
* @return
*/
private String _getSessionKey(FacesContext context)
{
String viewId = context.getViewRoot().getViewId();
StringBuilder builder = new StringBuilder(viewId.length() +
_COMPONENT_CHANGES_MAP_FOR_SESSION_KEY.length());
return builder.append(_COMPONENT_CHANGES_MAP_FOR_SESSION_KEY).append(viewId).toString();
}
/**
* Tracks the component changes for a particular view as well as all the movement
* changes so that component aliasing can be tracked
*/
private static final class ChangesForView implements Serializable
{
protected ChangesForView(boolean rw)
{
if (rw)
{
_componentChangesForView = new ConcurrentLinkedQueue();
_renameChanges = new CopyOnWriteArrayList();
}
else
{
_componentChangesForView = CollectionUtils.emptyQueue();
_renameChanges = Collections.emptyList();
}
}
@Override
public String toString()
{
return super.toString() + "[componentChange=" + _componentChangesForView +
" renameChanges=" + _renameChanges + "]";
}
@Override
public boolean equals(Object o)
{
if (o == this)
return true;
if (!(o instanceof ChangesForView))
return false;
ChangesForView other = (ChangesForView)o;
return _componentChangesForView.equals(other._componentChangesForView) &&
_renameChanges.equals(other._renameChanges);
}
@Override
public int hashCode()
{
return _componentChangesForView.hashCode() + 37 * _renameChanges.hashCode();
}
/**
* Returns the QualifiedComponentChanges for this viewId
*/
protected Iterable getComponentChangesForView()
{
return _componentChangesForView;
}
/**
* Adds a change to the QualifiedComponentChanges for this viewId, handling
* MoveChildComponentChanges specially to handle cases where the clientId
* of a component changes as a result of a rename operation
*/
protected void addChange(QualifiedComponentChange qualifiedChange)
{
// make sure that we don't add changes while getAttrChanges() is rebuilding the
// per-component changes
_componentChangesForView.add(qualifiedChange);
ComponentChange componentChange = qualifiedChange.getComponentChange();
if (componentChange instanceof AttributeComponentChange)
{
// update the attribute changes with the current change
_updateAttributeChange(_attrChanges, _renameMap, qualifiedChange);
}
else if (componentChange instanceof MoveChildComponentChange)
{
// we only need to remove moves that actually changed the absolute scoped id of the
// component
MoveChildComponentChange moveComponentChange = (MoveChildComponentChange)componentChange;
if (!moveComponentChange.getSourceScopedId().equals(moveComponentChange.getDestinationScopedId()))
{
_renameChanges.add(moveComponentChange);
// update the rename map to account for this change
_updateRenameMap(_renameMap, moveComponentChange);
}
}
}
/**
* Removes any previously stored attribute change for this component and attribute. Any moves
* later to an earlier addition of attribute change is taken care of, by means of removing such
* attribute change.
* @return The attribute change that was removed
*/
protected AttributeComponentChange removeAttributeChange(
String logicalScopedId,
AttributeComponentChange attributeChange)
{
// remove this change from the attribute changes map that gets used for applying simple
// component changes
return _removeAttributeChange(_attrChanges,
_renameMap,
logicalScopedId,
attributeChange);
}
/**
* Returns the Iterator of rename changes that affect the current scoped id in ComponentChange order
* @return
*/
protected Iterator getRenameChanges(String targetScopedId)
{
if (!_renameChanges.isEmpty())
{
String currTargetScopedId = targetScopedId;
List renameChanges = null;
// iterate from the back of the List determining the MoveChildComponentChange
// that are aliased to this scoped id
ListIterator moveChanges =
_renameChanges.listIterator(_renameChanges.size());
while (moveChanges.hasPrevious())
{
MoveChildComponentChange currMoveChange = moveChanges.previous();
if (currTargetScopedId.equals(currMoveChange.getDestinationScopedId()))
{
// lazily create the list the first time we need it
if (renameChanges == null)
renameChanges = new ArrayList();
renameChanges.add(currMoveChange);
// get the new id to search for
currTargetScopedId = currMoveChange.getSourceScopedId();
}
}
if (renameChanges != null)
{
if (renameChanges.size() > 1)
{
// reverse the list to match the order that we will see these items when traversing
// the changes from the forward direction
Collections.reverse(renameChanges);
}
return renameChanges.iterator();
}
}
return CollectionUtils.emptyIterator();
}
/**
* Apply the attribute changes for this component
* @param context
* @param component
*/
protected void applySimpleComponentChanges(FacesContext context, UIComponent component)
{
// Simple component changes always use logical scoped ids because they are consistent across
// all phases including tag execution
String scopedId = ComponentUtils.getLogicalScopedIdForComponent(component, context.getViewRoot());
ConcurrentMap attributeCmponentChanges = _attrChanges.get(scopedId);
if (attributeCmponentChanges != null)
{
for (ComponentChange change : attributeCmponentChanges.values())
{
change.changeComponent(component);
}
}
}
private void _updateAttributeChange(
ConcurrentMap> attrChanges,
ConcurrentMap renameMap,
QualifiedComponentChange qAttrChange)
{
// get all the attribute changes for this component after considering possible moves.
// We want to update, so create if necessary
ConcurrentMap changesForComponent =
_getAttributeChangesAfterHandlingMove(attrChanges,
renameMap,
qAttrChange.getTargetComponentLogicalScopedId(),
true);
AttributeComponentChange attrChange = (AttributeComponentChange)
qAttrChange.getComponentChange();
// update the current AttributeComponentChange for this attribute
String attrName = attrChange.getAttributeName();
changesForComponent.put(attrName, attrChange);
}
private AttributeComponentChange _removeAttributeChange(
ConcurrentMap> attrChanges,
ConcurrentMap renameMap,
String logicalScopedId,
AttributeComponentChange componentChange)
{
// get all the attribute changes for this component after considering possible moves
ConcurrentMap changesForComponent =
_getAttributeChangesAfterHandlingMove(attrChanges, renameMap, logicalScopedId, false);
if (changesForComponent != null)
{
return changesForComponent.remove(componentChange.getAttributeName());
}
return null;
}
/**
* Gets the map of all the attribute changes for a component with the supplied logical scoped id.
*
* @param attrChanges The map of attribute change collection per component keyed by the id
* @param renameMap The id renaming map for moved components
* @param logicalScopedId The logical scoped id of the component for which attribute changes are
* needed
* @param createIfNecessary Create the attribute changes map if there are no attribute changes
* for the component with supplied logical scoped id.
* @return The map of attribute changes keyed by the attribute name
*/
private ConcurrentMap _getAttributeChangesAfterHandlingMove(
ConcurrentMap> attrChanges,
ConcurrentMap renameMap,
String logicalScopedId,
boolean createIfNecessary)
{
// handle renames due to possible moves and get the original id
String originalScopedId = renameMap.get(logicalScopedId);
// we don't add rename mapping until a move, so if there is no entry, the component did not
// move, the original value is good
if (originalScopedId == null)
{
originalScopedId = logicalScopedId;
}
// get the map of attribute changes for this component, creating one if necessary
ConcurrentMap changesForComponent =
attrChanges.get(originalScopedId);
// if we haven't registered a Map yet, create one and register it
if (changesForComponent == null && createIfNecessary)
{
// =-= bts There probably aren't that many different changes per component. Maybe
// we need something smaller and more efficient here
changesForComponent = new ConcurrentHashMap();
attrChanges.put(originalScopedId, changesForComponent);
}
return changesForComponent;
}
/**
* Update the renamemap with a change
* @param renameMap
* @param moveChange
*/
private void _updateRenameMap(
ConcurrentMap renameMap,
MoveChildComponentChange moveChange)
{
String sourceScopedId = moveChange.getSourceLogicalScopedId();
String destinationScopedId = moveChange.getDestinationLogicalScopedId();
// we only need to update the map if we actually changed scoped ids
if (!(sourceScopedId.equals(destinationScopedId)))
{
// remove the old mapping for source
String originalScodeId = renameMap.remove(sourceScopedId);
// we don't bother adding mappings if there has been no rename, plus there might
// not be any attribute changes yet. In this case, the source scoped id must
// be the original id
if (originalScodeId == null)
originalScodeId = sourceScopedId;
// add the new, updated mapping to account for the move
renameMap.put(destinationScopedId, originalScodeId);
}
}
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException
{
throw new InvalidObjectException("proxy required");
}
private Object writeReplace()
{
return new SerializationProxy(this);
}
private static class SerializationProxy implements Serializable
{
SerializationProxy(ChangesForView changesForView)
{
_componentChangesForView =
new ArrayList(changesForView._componentChangesForView);
if (changesForView._renameChanges==Collections.EMPTY_LIST)
_rw = false;
else
_rw = true;
}
private Object readResolve()
{
ChangesForView changesForView = new ChangesForView(_rw);
for (QualifiedComponentChange qualifiedChange : _componentChangesForView)
{
changesForView.addChange(qualifiedChange);
}
return changesForView;
}
private final List _componentChangesForView;
private final boolean _rw;
private static final long serialVersionUID = 1L;
}
private final Queue _componentChangesForView;
private final List _renameChanges;
// map of original scopedIds to Map of attribute names and their new values. This allows
// us to apply all of attribute changes efficiently
private final ConcurrentMap> _attrChanges =
new ConcurrentHashMap>();
// map of current scoped ids to original scoped ids. This enables us to correctly update
// the attributes for the original scoped ids even after the component has moved
private final ConcurrentMap _renameMap =
new ConcurrentHashMap();
private static final long serialVersionUID = 1L;
}
private static final ChangesForView _EMPTY_CHANGES = new ChangesForView(false);
private static class QualifiedComponentChange implements Serializable
{
public QualifiedComponentChange(String targetComponentScopedId,
String targetComponentLogicalScopedId,
ComponentChange componentChange)
{
// NO-TRANS : Class is private and inner, no translated message required
if (targetComponentScopedId == null || componentChange == null)
throw new IllegalArgumentException("Target component scoped id and " +
"component change is required");
_targetComponentScopedId = targetComponentScopedId;
_targetComponentLogicalScopedId = (targetComponentScopedId.equals(targetComponentLogicalScopedId)) ? null :
targetComponentLogicalScopedId;
_componentChange = componentChange;
}
public String getTargetComponentScopedId()
{
return _targetComponentScopedId;
}
public String getTargetComponentLogicalScopedId()
{
return _targetComponentLogicalScopedId != null ? _targetComponentLogicalScopedId : _targetComponentScopedId;
}
public ComponentChange getComponentChange()
{
return _componentChange;
}
@Override
public boolean equals(Object o)
{
if (o == this)
return true;
if (!(o instanceof QualifiedComponentChange))
return false;
QualifiedComponentChange other = (QualifiedComponentChange)o;
return getTargetComponentLogicalScopedId().equals(other.getTargetComponentLogicalScopedId()) &&
_componentChange.equals(other._componentChange);
}
@Override
public int hashCode()
{
return getTargetComponentLogicalScopedId().hashCode() + 37 * _componentChange.hashCode();
}
@Override
public String toString()
{
return super.toString() + "[target=" + _targetComponentScopedId + " logical_target=" + getTargetComponentLogicalScopedId() +
" change=" + _componentChange + "]";
}
private final String _targetComponentScopedId;
private final String _targetComponentLogicalScopedId;
private final ComponentChange _componentChange;
private static final long serialVersionUID = 1L;
}
private static final String _COMPONENT_CHANGES_MAP_FOR_SESSION_KEY =
"org.apache.myfaces.trinidadinternal.ComponentChangesMapForSession";
private static final TrinidadLogger _LOG =
TrinidadLogger.createTrinidadLogger(SessionChangeManager.class);
}