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.HashMap;
22  
23  import javax.el.ValueExpression;
24  
25  import javax.faces.component.NamingContainer;
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  
32  
33  /**
34   * The base class for all ChangeManagers.
35   * A ChangeManager should manage accumulation of Changes and also
36   *  take care of their persistence.
37   * @version $Name:  $ ($Revision: adfrt/faces/adf-faces-api/src/main/java/oracle/adf/view/faces/change/ChangeManager.java#0 $) $Date: 10-nov-2005.19:09:58 $
38   */
39  public abstract class ChangeManager
40  {
41    public static void registerDocumentFactory(
42      String targetClassName,
43      String converterClassName)
44    {
45      if ((targetClassName == null) || (targetClassName.length() == 0))
46        throw new IllegalArgumentException(_LOG.getMessage(
47          "TARGET_CLASS_NAME_MUST_BE_PROVIDED"));
48  
49      if ((converterClassName == null) || (converterClassName.length() == 0))
50        throw new IllegalArgumentException(_LOG.getMessage(
51          "CONVERTER_CLASS_NAME_MUST_BE_PROVIDED"));
52  
53      synchronized (_CLASSNAME_TO_CONVERTER_NAME_MAP)
54      {
55        _CLASSNAME_TO_CONVERTER_NAME_MAP.put(targetClassName, converterClassName);
56      }
57    }
58  
59    /**
60     * Use the conversion rules to attempt to retrieve the equivalent
61     * document change for a ComponentChange
62     * @param change to convert
63     */
64    protected static DocumentChange createDocumentChange(
65      ComponentChange change)
66    {
67      Class<? extends ComponentChange> changeClass = change.getClass();
68  
69      Object converterObject = null;
70      DocumentChangeFactory converter = null;
71  
72      synchronized (_CLASS_TO_CONVERTER_MAP)
73      {
74        converterObject = _CLASS_TO_CONVERTER_MAP.get(changeClass);
75      }
76  
77      if (converterObject != null)
78      {
79        converter = (DocumentChangeFactory)converterObject;
80      }
81      else
82      {
83        String converterName = null;
84  
85        synchronized (_CLASSNAME_TO_CONVERTER_NAME_MAP)
86        {
87         converterName = 
88                    _CLASSNAME_TO_CONVERTER_NAME_MAP.get(changeClass.getName());
89        }
90  
91        if (converterName != null)
92        {
93          try
94          {
95            ClassLoader contextClassLoader =
96              Thread.currentThread().getContextClassLoader();
97  
98            Class<?> converterClass = contextClassLoader.loadClass(converterName);
99            if (DocumentChangeFactory.class.isAssignableFrom(converterClass))
100           {
101             converter = (DocumentChangeFactory)converterClass.newInstance();
102 
103             synchronized (_CLASS_TO_CONVERTER_MAP)
104             {
105               _CLASS_TO_CONVERTER_MAP.put(changeClass, converter);
106             }
107           }
108           else
109           {
110             // log warning because class isn't correct type
111             _LOG.warning("CONVERSION_CLASS_TYPE", new Object[] {converterClass, DocumentChangeFactory.class}); // NOTRANS
112           }
113         }
114         catch (Throwable e)
115         {
116           _LOG.warning("UNABLE_INSTANTIATE_CONVERTERCLASS", converterName); // NOTRANS
117           _LOG.warning(e);
118         }
119 
120         // if the registered converter class name doesn't work remove
121         // it from _CLASSNAME_TO_CONVERT_NAME_MAP
122         if (converter == null)
123         {
124           // this entry doesn't work, so remove it
125           _CLASSNAME_TO_CONVERTER_NAME_MAP.remove(converterName);
126 
127           return null;
128         }
129       }
130     }
131 
132     // return the converted object
133     if (converter != null)
134       return converter.convert(change);
135     
136     return null;
137   }
138 
139   /**
140    * Add a ComponentChange to this current request for a specified component.
141    * When called we will disallow changes if the component or its any ancestor 
142    * is a stamped component by UIXIterator. 
143    *
144    * @throws IllegalArgumentException if any of the supplied parameters were to
145    *          be null.
146    */
147   public abstract void addComponentChange(
148     FacesContext facesContext,
149     UIComponent uiComponent,
150     ComponentChange change);
151   
152   /**
153    * Replace an AttributeComponentChange if it's present. 
154    * 
155    * @param facesContext
156    * @param uiComponent
157    * @param attributeComponentChange
158    * @return the old change instance
159    */
160   public AttributeComponentChange replaceAttributeChangeIfPresent(FacesContext facesContext,
161     UIComponent uiComponent,
162     AttributeComponentChange attributeComponentChange)
163   {    
164     _LOG.warning("Must be implemented by subclass");
165     return null;
166   }  
167 
168   /**
169    * Add a DocumentChange to this current request for a specified component.
170    * When called we will allow changes even if the component or its any ancestor 
171    * is a stamped component by UIXIterator.
172    * 
173    * @throws IllegalArgumentException if any of the supplied parameters were to
174    *          be null.
175    */
176   public void addDocumentChange(
177       FacesContext facesContext,
178       UIComponent uiComponent,
179       DocumentChange change)
180   {
181     if (facesContext == null || uiComponent == null || change == null)
182       throw new IllegalArgumentException(_LOG.getMessage(
183         "CANNOT_ADD_CHANGE_WITH_FACECONTEXT_OR_UICOMPONENT_OR_NULL"));
184   }
185 
186   /**
187    * Applies all the ComponentChanges added so far for the current view.
188    * Developers should not need to call this method. Internal implementation
189    * will call it as the component tree is built and is ready to take changes.
190    * @param facesContext The FacesContext instance for the current request.
191    */
192   public void applyComponentChangesForCurrentView(FacesContext facesContext)
193   {
194     throw new UnsupportedOperationException("Subclassers must implement");
195   }
196 
197   /**
198    * Applies the ComponentChanges added so far for components underneath
199    * the specified NamingContainer.
200    * Developers should not need to call this method. Internal implementation
201    * will call it as the component tree is built and is ready to take changes.
202    * @param facesContext The FacesContext instance for the current request.
203    * @param root The NamingContainer that contains the component subtree
204    * to which ComponentChanges should be applied.  If null, all changes are
205    * applied.
206    * @throws IllegalArgumentException if the root NamingContainer is not a
207    *   UIComponent instance.
208    */
209   public void applyComponentChangesForSubtree(
210     FacesContext facesContext,
211     NamingContainer root
212     )
213   {
214     throw new UnsupportedOperationException("Subclassers must implement");
215   }
216 
217   /**
218    * Apply non-cross-component changes to a component in its original location.  This is typically
219    * only called by tags that need to ensure that a newly created component instance is
220    * as up-to-date as possible.
221    * @param context
222    * @param component Component to apply the simple changes to
223    */
224   public void applySimpleComponentChanges(FacesContext context, UIComponent component)
225   {
226     throw new UnsupportedOperationException("Subclassers must implement");    
227   }
228   
229   private static class AttributeConverter extends DocumentChangeFactory
230   {
231     @Override
232     public DocumentChange convert(ComponentChange compChange)
233     {
234       if (compChange instanceof AttributeComponentChange)
235       {
236         AttributeComponentChange change = (AttributeComponentChange)compChange;
237 
238         Object value = change.getAttributeValue();
239 
240         // =-= bts TODO add registration of attribute converters
241         String valueString = null;
242         if ((value == null) ||
243             (value instanceof CharSequence) ||
244             (value instanceof Number) ||
245             (value instanceof Boolean))
246         {
247           valueString = (value != null)? value.toString() : null;
248         }
249         else if (value instanceof ValueExpression)
250         {
251           valueString = ((ValueExpression)value).getExpressionString();
252         }
253         else if (value instanceof ValueBinding)
254         {
255           valueString = ((ValueBinding)value).getExpressionString();
256         }
257         
258         if (valueString != null)
259           return new AttributeDocumentChange(change.getAttributeName(),
260                                              valueString);
261       }
262 
263       // no conversion possible
264       return null;
265     }
266   }
267 
268   private static HashMap<String, String> _CLASSNAME_TO_CONVERTER_NAME_MAP =
269     new HashMap<String, String>();
270   
271   private static HashMap<Class<? extends ComponentChange>, DocumentChangeFactory> _CLASS_TO_CONVERTER_MAP = 
272     new HashMap<Class<? extends ComponentChange>, DocumentChangeFactory>();
273 
274   static private final TrinidadLogger _LOG = 
275      TrinidadLogger.createTrinidadLogger(ChangeManager.class);
276 
277   static
278   {
279     // register the attribute converter
280     _CLASS_TO_CONVERTER_MAP.put(AttributeComponentChange.class,
281                                 new AttributeConverter());
282   }
283 
284 }