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  // WHENEVER A NEW METHOD IS ADDED TO THIS CLASS, REMEMBER TO ADD AN IMPLEMENTATION TO
40  // org.apache.myfaces.trinidad.change.ChangeManagerWrapper
41  public abstract class ChangeManager
42  {
43    public static void registerDocumentFactory(
44      String targetClassName,
45      String converterClassName)
46    {
47      if ((targetClassName == null) || (targetClassName.length() == 0))
48        throw new IllegalArgumentException(_LOG.getMessage(
49          "TARGET_CLASS_NAME_MUST_BE_PROVIDED"));
50  
51      if ((converterClassName == null) || (converterClassName.length() == 0))
52        throw new IllegalArgumentException(_LOG.getMessage(
53          "CONVERTER_CLASS_NAME_MUST_BE_PROVIDED"));
54  
55      synchronized (_CLASSNAME_TO_CONVERTER_NAME_MAP)
56      {
57        _CLASSNAME_TO_CONVERTER_NAME_MAP.put(targetClassName, converterClassName);
58      }
59    }
60  
61    /**
62     * Use the conversion rules to attempt to retrieve the equivalent
63     * document change for a ComponentChange
64     * @param change to convert
65     */
66    protected static DocumentChange createDocumentChange(
67      ComponentChange change)
68    {
69      // the supplied ComponentChange could implement DocumentChange
70      if (change instanceof DocumentChange)
71      {
72        return (DocumentChange)change;
73      }
74      
75      Class<? extends ComponentChange> changeClass = change.getClass();
76  
77      Object converterObject = null;
78      DocumentChangeFactory converter = null;
79  
80      synchronized (_CLASS_TO_CONVERTER_MAP)
81      {
82        converterObject = _CLASS_TO_CONVERTER_MAP.get(changeClass);
83      }
84  
85      if (converterObject != null)
86      {
87        converter = (DocumentChangeFactory)converterObject;
88      }
89      else
90      {
91        String converterName = null;
92  
93        synchronized (_CLASSNAME_TO_CONVERTER_NAME_MAP)
94        {
95         converterName = 
96                    _CLASSNAME_TO_CONVERTER_NAME_MAP.get(changeClass.getName());
97        }
98  
99        if (converterName != null)
100       {
101         try
102         {
103           ClassLoader contextClassLoader =
104             Thread.currentThread().getContextClassLoader();
105 
106           Class<?> converterClass = contextClassLoader.loadClass(converterName);
107           if (DocumentChangeFactory.class.isAssignableFrom(converterClass))
108           {
109             converter = (DocumentChangeFactory)converterClass.newInstance();
110 
111             synchronized (_CLASS_TO_CONVERTER_MAP)
112             {
113               _CLASS_TO_CONVERTER_MAP.put(changeClass, converter);
114             }
115           }
116           else
117           {
118             // log warning because class isn't correct type
119             _LOG.warning("CONVERSION_CLASS_TYPE", new Object[] {converterClass, DocumentChangeFactory.class});
120           }
121         }
122         catch (Throwable e)
123         {
124           _LOG.warning("UNABLE_INSTANTIATE_CONVERTERCLASS", converterName); // NOTRANS
125           _LOG.warning(e);
126         }
127 
128         // if the registered converter class name doesn't work remove
129         // it from _CLASSNAME_TO_CONVERT_NAME_MAP
130         if (converter == null)
131         {
132           // this entry doesn't work, so remove it
133           _CLASSNAME_TO_CONVERTER_NAME_MAP.remove(converterName);
134 
135           return null;
136         }
137       }
138     }
139 
140     // return the converted object
141     if (converter != null)
142       return converter.convert(change);
143     
144     return null;
145   }
146 
147   /**
148    * Adds a ComponentChange to the current request for a specified component. Component changes 
149    *  cannot be added for stamped children of an UIXIterator.
150    * 
151    * A DocumentChange will be automatically created and applied on the ChangeManager registered
152    *  for this application if the following conditions are met:
153    *  1. The ChangeManager registered for the application supports document change persistence
154    *  2. DocumentChange corresponding to the supplied ComponentChange can be created with the help
155    *      of any registered DocumentChangeFactory
156    * When such a DocumentChange is added, the ChangeManager registered for the application is
157    *  notified by means of calling its documentChangeApplied() method. This is to give the 
158    *  registered ChangeManager an opportunity to take any necessary action. For example, Session 
159    *  based ChangeManager implementations may choose to remove the ComponentChange, if any added 
160    *  earlier. Custom ChangeManager implementations should notify likewise if it automatically 
161    *  creates and adds a DocumentChange.
162    * 
163    * @throws IllegalArgumentException if any of the supplied parameters were to be null.
164    * 
165    * @see DocumentChangeFactory
166    * @see #documentChangeApplied(FacesContext, UIComponent, ComponentChange
167    */
168   public abstract void addComponentChange(
169     FacesContext facesContext,
170     UIComponent uiComponent,
171     ComponentChange change);
172 
173   /**
174    * Replace an AttributeComponentChange if it's present. 
175    * 
176    * @param facesContext
177    * @param uiComponent
178    * @param attributeComponentChange
179    * @return the old change instance
180    */
181   public AttributeComponentChange replaceAttributeChangeIfPresent(
182     FacesContext facesContext,
183     UIComponent uiComponent,
184     AttributeComponentChange attributeComponentChange)
185   {    
186     _LOG.warning("Must be implemented by subclass");
187     return null;
188   }  
189 
190   /**
191    * Add a DocumentChange to this current request for a specified component.
192    * When called we will allow changes even if the component or its any ancestor
193    * is a stamped component by UIXIterator.
194    *
195    * @throws IllegalArgumentException if any of the supplied parameters were to
196    * be null.
197    *
198    * @deprecated use
199    * {@link ChangeManager#addDocumentChangeWithOutcome(javax.faces.context.FacesContext,javax.faces.component.UIComponent,org.apache.myfaces.trinidad.change.DocumentChange)}
200    * instead
201    */
202   @Deprecated
203   public void addDocumentChange(
204     FacesContext facesContext,
205     UIComponent uiComponent,
206     DocumentChange change)
207   {
208     if (facesContext == null || uiComponent == null || change == null)
209       throw new IllegalArgumentException(_LOG.getMessage(
210         "CANNOT_ADD_CHANGE_WITH_FACECONTEXT_OR_UICOMPONENT_OR_NULL"));
211   }
212   
213   /**
214    * Add a DocumentChange for a specified component, and return the outcome of adding the change.
215    * 
216    * @param facesContext  The FacesContext instance for the current request
217    * @param uiComponent   The UIComponent instance for which the DocumentChange is to be added
218    * @param change        The DocumentChange to be added
219    * 
220    * @return The outcome of adding the document change
221    * 
222    * @throws IllegalArgumentException if any of the supplied parameters were to
223    *          be null.
224    *          
225    * @see ChangeOutcome
226    */
227   public ChangeOutcome addDocumentChangeWithOutcome(
228     FacesContext facesContext,
229     UIComponent uiComponent,
230     DocumentChange change)
231   {
232     addDocumentChange(facesContext, uiComponent, change);
233 
234     return ChangeOutcome.UNKNOWN;
235   }
236 
237   /**
238    * This method is called on the registered ChangeManager if a ChangeManager in its 
239    *  addComponentChange() implementation automatically creates an equivalent DocumentChange and
240    *  applies the change. The registered ChangeManager may choose to take some action based on 
241    *  the outcome of applying the document change. For example, session based ChangeManager
242    *  implementations may choose to remove any earlier added ComponentChange if an equivalent
243    *  document change is now successfully applied
244    *  
245    * @param component       The target UIComponent instance for which the DocumentChange was
246    *                         applied
247    * @param componentChange The ComponentChange for which an equivalent DocumentChange was applied
248    * 
249    * @return The outcome of handling this notification
250    * 
251    * @throws IllegalArgumentException if the supplied ComponentChange is null.   *          
252    */
253   public NotificationOutcome documentChangeApplied(
254     FacesContext facesContext,
255     UIComponent component,
256     ComponentChange componentChange)
257   {
258     if (componentChange == null)
259       throw new IllegalArgumentException("The supplied ComponentChange object is null"); 
260     return NotificationOutcome.NOT_HANDLED;
261   }
262   
263   /**
264    * Applies all the ComponentChanges added so far for the current view.
265    * Developers should not need to call this method. Internal implementation
266    * will call it as the component tree is built and is ready to take changes.
267    * @param facesContext The FacesContext instance for the current request.
268    */
269   public void applyComponentChangesForCurrentView(FacesContext facesContext)
270   {
271     throw new UnsupportedOperationException("Subclassers must implement");
272   }
273 
274   /**
275    * Applies the ComponentChanges added so far for components underneath
276    * the specified NamingContainer.
277    * Developers should not need to call this method. Internal implementation
278    * will call it as the component tree is built and is ready to take changes.
279    * @param facesContext The FacesContext instance for the current request.
280    * @param root The NamingContainer that contains the component subtree
281    * to which ComponentChanges should be applied.  If null, all changes are
282    * applied.
283    * @throws IllegalArgumentException if the root NamingContainer is not a
284    *   UIComponent instance.
285    */
286   public void applyComponentChangesForSubtree(
287     FacesContext facesContext,
288     NamingContainer root)
289   {
290     throw new UnsupportedOperationException("Subclassers must implement");
291   }
292   
293   /**
294    * Apply non-cross-component changes to a component in its original location.  This is typically
295    * only called by tags that need to ensure that a newly created component instance is
296    * as up-to-date as possible.
297    * @param context
298    * @param component Component to apply the simple changes to
299    */
300   public void applySimpleComponentChanges(FacesContext context, UIComponent component)
301   {
302     throw new UnsupportedOperationException("Subclassers must implement");    
303   }
304   
305   /**
306    * Indicates the outcome of the attempt to apply a Change. Possible outcomes are:
307    * 1. UNKNOWN - We do not know if the change was applied or not
308    * 2. CHANGE_APPLIED - Change was successfully applied
309    * 3. CHANGE_NOT_APPLIED - There was a failure when applying the Change
310    *
311    * @see #addDocumentChangeWithOutcome(FacesContext,UIComponent,DocumentChange)
312    */
313   public static enum ChangeOutcome
314   {
315     UNKNOWN,
316     CHANGE_APPLIED,
317     CHANGE_NOT_APPLIED;
318 
319     private static final long serialVersionUID = 1L;
320   }
321 
322   /**
323    * Indicates whether the notification was handled:
324    * 1. HANDLED - Notification was handled
325    * 2. NOT_HANDLED - Notification was not handled
326    * 
327    * @see #documentChangeApplied(FacesContext, UIComponent, ComponentChange)
328    */
329   public static enum NotificationOutcome
330   {
331     HANDLED,
332     NOT_HANDLED;
333 
334     private static final long serialVersionUID = 1L;
335   }
336   
337   private static class AttributeConverter extends DocumentChangeFactory
338   {
339     @Override
340     public DocumentChange convert(ComponentChange compChange)
341     {
342       if (compChange instanceof AttributeComponentChange)
343       {
344         AttributeComponentChange change = (AttributeComponentChange)compChange;
345 
346         Object value = change.getAttributeValue();
347 
348         // =-= bts TODO add registration of attribute converters
349         String valueString = null;
350         if ((value == null) ||
351             (value instanceof CharSequence) ||
352             (value instanceof Number) ||
353             (value instanceof Boolean))
354         {
355           valueString = (value != null)? value.toString() : null;
356         }
357         else if (value instanceof ValueExpression)
358         {
359           valueString = ((ValueExpression)value).getExpressionString();
360         }
361         else if (value instanceof ValueBinding)
362         {
363           valueString = ((ValueBinding)value).getExpressionString();
364         }
365         
366         if (valueString != null)
367           return new AttributeDocumentChange(change.getAttributeName(),
368                                              valueString);
369       }
370 
371       // no conversion possible
372       return null;
373     }
374   }
375 
376   private static HashMap<String, String> _CLASSNAME_TO_CONVERTER_NAME_MAP =
377     new HashMap<String, String>();
378   
379   private static HashMap<Class<? extends ComponentChange>, DocumentChangeFactory> _CLASS_TO_CONVERTER_MAP = 
380     new HashMap<Class<? extends ComponentChange>, DocumentChangeFactory>();
381 
382   static private final TrinidadLogger _LOG = 
383      TrinidadLogger.createTrinidadLogger(ChangeManager.class);
384 
385   static
386   {
387     // register the attribute converter
388     _CLASS_TO_CONVERTER_MAP.put(AttributeComponentChange.class,
389                                 new AttributeConverter());
390   }
391 
392 }