View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.trinidad.change;
20  
21  import java.util.Map;
22  
23  import javax.el.ValueExpression;
24  
25  import javax.faces.component.ContextCallback;
26  import javax.faces.component.UIComponent;
27  import javax.faces.context.FacesContext;
28  import javax.faces.el.ValueBinding;
29  
30  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
31  import org.apache.myfaces.trinidad.model.RowKeySet;
32  
33  /**
34   * Handles RowKeySetAttribute changes, which need to be handled specially because they are mutable
35   * and programmers assume that the instances don't change.
36   * 
37   * Cases that we need to worry about:
38   * 1) old value is null
39   * 2) new value is null (should clear existing RowKeySet if possible)
40   * 3) old value = new value
41   * 4) new value is a ValueExpression or ValueBinding
42   * 5) old value is a ValueExpression that needs to be evaluated in context
43   * 6) RowKeySet is internally bound to a model that needs to be evaluated in context
44   */
45  public final class RowKeySetAttributeChange extends AttributeComponentChange
46  {
47    public RowKeySetAttributeChange(String clientId,  String propertyName, Object value)
48    {
49      super(propertyName, value);
50      
51      if ((clientId == null) || (clientId.length() == 0))
52        throw new IllegalArgumentException("No clientId specified");
53  
54      _clientId = clientId;
55    }
56  
57    @Override
58    @SuppressWarnings("deprecation")
59    public void changeComponent(UIComponent component)
60    {
61      Map<String, Object> attributeMap = component.getAttributes();
62  
63      Object newAttributeValue = getAttributeValue();
64      String attrName  = getAttributeName();
65      
66      if ((newAttributeValue instanceof RowKeySet) || (newAttributeValue == null))
67      {
68        // Specially handle RowKeySet case by replacing the contents of the RowKeySet in-place
69        // rather than replacing the entire object.  This keeps the mutable object instance from
70        // changing
71        _updateRowKeySetInPlace(component, attrName, (RowKeySet)newAttributeValue);
72      }
73      else if (newAttributeValue instanceof ValueExpression)
74      {
75        // if the new attribute value is a ValueExpession, set it and remove the old value
76        // so that the ValueExpression takes precedence
77        component.setValueExpression(attrName, (ValueExpression)newAttributeValue);
78        attributeMap.remove(attrName);
79      }
80      else if (newAttributeValue instanceof ValueBinding)
81      {
82        // if the new attribute value is a ValueBinding, set it and remove the old value
83        // so that the ValueBinding takes precedence
84        component.setValueBinding(attrName, (ValueBinding)newAttributeValue);
85        attributeMap.remove(attrName);
86      }
87      else
88      {
89        // perform the default behavior
90        attributeMap.put(attrName, newAttributeValue);
91      }
92    }
93  
94    private void _updateRowKeySetInPlace(UIComponent component, String attrName, RowKeySet newValue)
95    {
96      ValueExpression oldExpression = component.getValueExpression(attrName);
97      
98      // due to bug in how the trinidad table and tree handle their RowKeySets, always use
99      // invoke on component and get the old value in context all of the time for now rather
100     // than trying to get the value directly if we don't have an expression
101     //use EL to get the oldValue and then determine whether we need to update in place
102     final FacesContext context = FacesContext.getCurrentInstance();
103                 
104     context.getViewRoot().invokeOnComponent(
105       context,
106       _clientId,
107       new GetOldValueAndUpdate(oldExpression, attrName, newValue));
108   }
109     
110   /**
111    * Get the oldValue in context and update it in context
112    */
113   private static final class GetOldValueAndUpdate implements ContextCallback
114   {
115     public GetOldValueAndUpdate(ValueExpression expression, String attributeName, RowKeySet newKeySet)
116     {
117       _expression = expression;
118       _attributeName = attributeName;
119       _newKeySet  = newKeySet;
120     }
121     
122     public void invokeContextCallback(FacesContext context,
123                                       UIComponent target)
124     {
125       Object oldValue;
126       
127       // due to bug in how tables and trees handle RowKeySet, temporarily support getting the
128       // old value in context, even when we don't have a value expression
129       if (_expression != null)
130         oldValue = _expression.getValue(context.getELContext());
131       else
132         oldValue = target.getAttributes().get(_attributeName);
133       
134       // update the old KeySet with the old and new values
135       _updateKeySet(target, oldValue);
136     }
137 
138     private void _updateKeySet(UIComponent component, Object oldValue)
139     {
140       // check for equality because otherwise we would clear ourselves and end up empty
141       if (oldValue != _newKeySet)
142       {
143         // if the old value is a RowKeySet, we can replace in place
144         if (oldValue instanceof RowKeySet)
145         {
146           RowKeySet oldKeySet = (RowKeySet)oldValue;
147           
148           try
149           {
150             oldKeySet.clear();
151             
152             if (_newKeySet != null)
153             {
154               oldKeySet.addAll(_newKeySet);
155             }
156           }
157           catch (Exception e)
158           {
159             _LOG.warning("FAILED_ROWKEYSETATTRIBUTECHANGE", e);
160             return;
161           }
162         }
163         else
164         {
165           // if the oldKeySet is null, just set the new keySet
166           component.getAttributes().put(_attributeName, _newKeySet);
167         }
168       }    
169     }
170     
171     private final ValueExpression _expression;
172     private final String _attributeName;
173     private final RowKeySet _newKeySet;
174   }
175 
176   private static final long serialVersionUID = 1L;
177   static private final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(RowKeySetAttributeChange.class);
178   private final String _clientId;
179 }