All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.myfaces.trinidad.change.ChangeManager Maven / Gradle / Ivy

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.util.HashMap;

import javax.el.ValueExpression;

import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;

import org.apache.myfaces.trinidad.logging.TrinidadLogger;


/**
 * The base class for all ChangeManagers.
 * A ChangeManager should manage accumulation of Changes and also
 *  take care of their persistence.
 * @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 $
 */
// WHENEVER A NEW METHOD IS ADDED TO THIS CLASS, REMEMBER TO ADD AN IMPLEMENTATION TO
// org.apache.myfaces.trinidad.change.ChangeManagerWrapper
public abstract class ChangeManager
{
  public static void registerDocumentFactory(
    String targetClassName,
    String converterClassName)
  {
    if ((targetClassName == null) || (targetClassName.length() == 0))
      throw new IllegalArgumentException(_LOG.getMessage(
        "TARGET_CLASS_NAME_MUST_BE_PROVIDED"));

    if ((converterClassName == null) || (converterClassName.length() == 0))
      throw new IllegalArgumentException(_LOG.getMessage(
        "CONVERTER_CLASS_NAME_MUST_BE_PROVIDED"));

    synchronized (_CLASSNAME_TO_CONVERTER_NAME_MAP)
    {
      _CLASSNAME_TO_CONVERTER_NAME_MAP.put(targetClassName, converterClassName);
    }
  }

  /**
   * Use the conversion rules to attempt to retrieve the equivalent
   * document change for a ComponentChange
   * @param change to convert
   */
  protected static DocumentChange createDocumentChange(
    ComponentChange change)
  {
    // the supplied ComponentChange could implement DocumentChange
    if (change instanceof DocumentChange)
    {
      return (DocumentChange)change;
    }
    
    Class changeClass = change.getClass();

    Object converterObject = null;
    DocumentChangeFactory converter = null;

    synchronized (_CLASS_TO_CONVERTER_MAP)
    {
      converterObject = _CLASS_TO_CONVERTER_MAP.get(changeClass);
    }

    if (converterObject != null)
    {
      converter = (DocumentChangeFactory)converterObject;
    }
    else
    {
      String converterName = null;

      synchronized (_CLASSNAME_TO_CONVERTER_NAME_MAP)
      {
       converterName = 
                  _CLASSNAME_TO_CONVERTER_NAME_MAP.get(changeClass.getName());
      }

      if (converterName != null)
      {
        try
        {
          ClassLoader contextClassLoader =
            Thread.currentThread().getContextClassLoader();

          Class converterClass = contextClassLoader.loadClass(converterName);
          if (DocumentChangeFactory.class.isAssignableFrom(converterClass))
          {
            converter = (DocumentChangeFactory)converterClass.newInstance();

            synchronized (_CLASS_TO_CONVERTER_MAP)
            {
              _CLASS_TO_CONVERTER_MAP.put(changeClass, converter);
            }
          }
          else
          {
            // log warning because class isn't correct type
            _LOG.warning("CONVERSION_CLASS_TYPE", new Object[] {converterClass, DocumentChangeFactory.class});
          }
        }
        catch (Throwable e)
        {
          _LOG.warning("UNABLE_INSTANTIATE_CONVERTERCLASS", converterName); // NOTRANS
          _LOG.warning(e);
        }

        // if the registered converter class name doesn't work remove
        // it from _CLASSNAME_TO_CONVERT_NAME_MAP
        if (converter == null)
        {
          // this entry doesn't work, so remove it
          _CLASSNAME_TO_CONVERTER_NAME_MAP.remove(converterName);

          return null;
        }
      }
    }

    // return the converted object
    if (converter != null)
      return converter.convert(change);
    
    return null;
  }

  /**
   * Adds a ComponentChange to the current request for a specified component. Component changes 
   *  cannot be added for stamped children of an UIXIterator.
   * 
   * A DocumentChange will be automatically created and applied on the ChangeManager registered
   *  for this application if the following conditions are met:
   *  1. The ChangeManager registered for the application supports document change persistence
   *  2. DocumentChange corresponding to the supplied ComponentChange can be created with the help
   *      of any registered DocumentChangeFactory
   * When such a DocumentChange is added, the ChangeManager registered for the application is
   *  notified by means of calling its documentChangeApplied() method. This is to give the 
   *  registered ChangeManager an opportunity to take any necessary action. For example, Session 
   *  based ChangeManager implementations may choose to remove the ComponentChange, if any added 
   *  earlier. Custom ChangeManager implementations should notify likewise if it automatically 
   *  creates and adds a DocumentChange.
   * 
   * @throws IllegalArgumentException if any of the supplied parameters were to be null.
   * 
   * @see DocumentChangeFactory
   * @see #documentChangeApplied(FacesContext, UIComponent, ComponentChange
   */
  public abstract void addComponentChange(
    FacesContext facesContext,
    UIComponent uiComponent,
    ComponentChange change);

  /**
   * Replace an AttributeComponentChange if it's present. 
   * 
   * @param facesContext
   * @param uiComponent
   * @param attributeComponentChange
   * @return the old change instance
   */
  public AttributeComponentChange replaceAttributeChangeIfPresent(
    FacesContext facesContext,
    UIComponent uiComponent,
    AttributeComponentChange attributeComponentChange)
  {    
    _LOG.warning("Must be implemented by subclass");
    return null;
  }  

  /**
   * Add a DocumentChange to this current request for a specified component.
   * When called we will allow changes even if the component or its any ancestor
   * is a stamped component by UIXIterator.
   *
   * @throws IllegalArgumentException if any of the supplied parameters were to
   * be null.
   *
   * @deprecated use
   * {@link ChangeManager#addDocumentChangeWithOutcome(javax.faces.context.FacesContext,javax.faces.component.UIComponent,org.apache.myfaces.trinidad.change.DocumentChange)}
   * instead
   */
  @Deprecated
  public void addDocumentChange(
    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"));
  }
  
  /**
   * Add a DocumentChange for a specified component, and return the outcome of adding the change.
   * 
   * @param facesContext  The FacesContext instance for the current request
   * @param uiComponent   The UIComponent instance for which the DocumentChange is to be added
   * @param change        The DocumentChange to be added
   * 
   * @return The outcome of adding the document change
   * 
   * @throws IllegalArgumentException if any of the supplied parameters were to
   *          be null.
   *          
   * @see ChangeOutcome
   */
  public ChangeOutcome addDocumentChangeWithOutcome(
    FacesContext facesContext,
    UIComponent uiComponent,
    DocumentChange change)
  {
    addDocumentChange(facesContext, uiComponent, change);

    return ChangeOutcome.UNKNOWN;
  }

  /**
   * This method is called on the registered ChangeManager if a ChangeManager in its 
   *  addComponentChange() implementation automatically creates an equivalent DocumentChange and
   *  applies the change. The registered ChangeManager may choose to take some action based on 
   *  the outcome of applying the document change. For example, session based ChangeManager
   *  implementations may choose to remove any earlier added ComponentChange if an equivalent
   *  document change is now successfully applied
   *  
   * @param component       The target UIComponent instance for which the DocumentChange was
   *                         applied
   * @param componentChange The ComponentChange for which an equivalent DocumentChange was applied
   * 
   * @return The outcome of handling this notification
   * 
   * @throws IllegalArgumentException if the supplied ComponentChange is null.   *          
   */
  public NotificationOutcome documentChangeApplied(
    FacesContext facesContext,
    UIComponent component,
    ComponentChange componentChange)
  {
    if (componentChange == null)
      throw new IllegalArgumentException("The supplied ComponentChange object is null"); 
    return NotificationOutcome.NOT_HANDLED;
  }
  
  /**
   * Applies all the ComponentChanges added so far for the current view.
   * Developers should not need to call this method. Internal implementation
   * will call it as the component tree is built and is ready to take changes.
   * @param facesContext The FacesContext instance for the current request.
   */
  public void applyComponentChangesForCurrentView(FacesContext facesContext)
  {
    throw new UnsupportedOperationException("Subclassers must implement");
  }

  /**
   * Applies the ComponentChanges added so far for components underneath
   * the specified NamingContainer.
   * Developers should not need to call this method. Internal implementation
   * will call it as the component tree is built and is ready to take changes.
   * @param facesContext The FacesContext instance for the current request.
   * @param root The NamingContainer that contains the component subtree
   * to which ComponentChanges should be applied.  If null, all changes are
   * applied.
   * @throws IllegalArgumentException if the root NamingContainer is not a
   *   UIComponent instance.
   */
  public void applyComponentChangesForSubtree(
    FacesContext facesContext,
    NamingContainer root)
  {
    throw new UnsupportedOperationException("Subclassers must implement");
  }
  
  /**
   * Apply non-cross-component changes to a component in its original location.  This is typically
   * only called by tags that need to ensure that a newly created component instance is
   * as up-to-date as possible.
   * @param context
   * @param component Component to apply the simple changes to
   */
  public void applySimpleComponentChanges(FacesContext context, UIComponent component)
  {
    throw new UnsupportedOperationException("Subclassers must implement");    
  }
  
  /**
   * Indicates the outcome of the attempt to apply a Change. Possible outcomes are:
   * 1. UNKNOWN - We do not know if the change was applied or not
   * 2. CHANGE_APPLIED - Change was successfully applied
   * 3. CHANGE_NOT_APPLIED - There was a failure when applying the Change
   *
   * @see #addDocumentChangeWithOutcome(FacesContext,UIComponent,DocumentChange)
   */
  public static enum ChangeOutcome
  {
    UNKNOWN,
    CHANGE_APPLIED,
    CHANGE_NOT_APPLIED;

    private static final long serialVersionUID = 1L;
  }

  /**
   * Indicates whether the notification was handled:
   * 1. HANDLED - Notification was handled
   * 2. NOT_HANDLED - Notification was not handled
   * 
   * @see #documentChangeApplied(FacesContext, UIComponent, ComponentChange)
   */
  public static enum NotificationOutcome
  {
    HANDLED,
    NOT_HANDLED;

    private static final long serialVersionUID = 1L;
  }
  
  private static class AttributeConverter extends DocumentChangeFactory
  {
    @Override
    public DocumentChange convert(ComponentChange compChange)
    {
      if (compChange instanceof AttributeComponentChange)
      {
        AttributeComponentChange change = (AttributeComponentChange)compChange;

        Object value = change.getAttributeValue();

        // =-= bts TODO add registration of attribute converters
        String valueString = null;
        if ((value == null) ||
            (value instanceof CharSequence) ||
            (value instanceof Number) ||
            (value instanceof Boolean))
        {
          valueString = (value != null)? value.toString() : null;
        }
        else if (value instanceof ValueExpression)
        {
          valueString = ((ValueExpression)value).getExpressionString();
        }
        else if (value instanceof ValueBinding)
        {
          valueString = ((ValueBinding)value).getExpressionString();
        }
        
        if (valueString != null)
          return new AttributeDocumentChange(change.getAttributeName(),
                                             valueString);
      }

      // no conversion possible
      return null;
    }
  }

  private static HashMap _CLASSNAME_TO_CONVERTER_NAME_MAP =
    new HashMap();
  
  private static HashMap, DocumentChangeFactory> _CLASS_TO_CONVERTER_MAP = 
    new HashMap, DocumentChangeFactory>();

  static private final TrinidadLogger _LOG = 
     TrinidadLogger.createTrinidadLogger(ChangeManager.class);

  static
  {
    // register the attribute converter
    _CLASS_TO_CONVERTER_MAP.put(AttributeComponentChange.class,
                                new AttributeConverter());
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy