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

net.sf.wcfart.wcf.form.FormComponent Maven / Gradle / Ivy

The newest version!
/*
 * ====================================================================
 * This software is subject to the terms of the Common Public License
 * Agreement, available at the following URL:
 *   http://www.opensource.org/licenses/cpl.html .
 * Copyright (C) 2003-2004 TONBELLER AG.
 * All Rights Reserved.
 * You must accept the terms of that agreement to use this software.
 * ====================================================================
 *
 * 
 */
package net.sf.wcfart.wcf.form;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.log4j.Logger;
import org.jaxen.JaxenException;
import org.jaxen.dom.DOMXPath;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import net.sf.wcfart.wcf.component.Component;
import net.sf.wcfart.wcf.component.FormListener;
import net.sf.wcfart.wcf.controller.RequestContext;
import net.sf.wcfart.wcf.controller.RequestListener;
import net.sf.wcfart.wcf.convert.ConvertException;
import net.sf.wcfart.wcf.format.FormatException;
import net.sf.wcfart.wcf.ui.XoplonCtrl;
import net.sf.wcfart.wcf.utils.DomUtils;
import net.sf.wcfart.wcf.utils.IdGenerator;
import net.sf.wcfart.wcf.utils.NoWarn;
import net.sf.wcfart.wcf.utils.SoftException;
import net.sf.wcfart.wcf.utils.XoplonNS;
import net.sf.wcfart.wcf.wizard.PageListener;
import net.sf.wcfart.wcf.wizard.WizardPage;
import net.sf.wcfart.wcf.wizard.WizardPageSupport;

/**
 * Manges a DOM that contains xoplon controls like <textField> etc
 * @author av
 */
public class FormComponent extends XmlComponent implements FormListener, WizardPage {
  private boolean dirty = true;
  boolean haveError = false;
  Object bean;
  boolean bookmarkable = false;
  boolean finishButton = true;
  WizardPageSupport wizardPageSupport = new WizardPageSupport(this);

  private static Logger logger = Logger.getLogger(FormComponent.class);

  /**
   * creates a FormComponent with beanModel == this
   * @param id id
	 * @param parent parent
   * @param document document
   */
  public FormComponent(String id, Component parent, Document document) {
    this(id, parent, document, null);
    this.bean = this;
  }

  /**
   * creates a form component
   * 
   * @param id id
   * @param parent parent
   * @param document document
   * @param bean bean
   */
  public FormComponent(String id, Component parent, Document document, Object bean) {
    super(id, parent, document);
    this.bean = bean;
    Element rootElem = document.getDocumentElement();
    if (rootElem.getAttribute("id").length() == 0)
      rootElem.setAttribute("id", id);
    new IdGenerator().generate(document, id + ".");
  }

  /**
   * fills the form with values from the bean
   */
  public void revert(RequestContext context) {
    super.revert(context);
    try {
      clearErrors();
      context.getConverter().revert(bean, getDocument());
      dirty = false;
      haveError = false;
    } catch (ConvertException e) {
      logger.error("exception caught", e);
      throw new SoftException(e);
    }
  }

  /**
   * fills bean and form from user input
   */
  public boolean validate(RequestContext context) {
    boolean success = super.validate(context);
    try {
      logger.info("enter");
      context.getConverter().validate(context.getParameters(), context.getFileParameters(),
          getDocument(), bean);
      return success;
    } catch (ConvertException e) {
      logger.error(null, e);
      throw new SoftException(e);
    } catch (FormatException e) {
      logger.info("invalid user input: " + e.getMessage());
      haveError = true;
      return false;
    }
  }

  /**
   * deferred ctor
   * check if the FormListener is removed from environment when this is removed from session
	 * @param context context
   */
  public void initialize(RequestContext context) throws Exception {
    super.initialize(context);
    initActionReferences();
    dirty = true;
    if (bean instanceof FormBean)
      ((FormBean) bean).setFormComponent(context, this);
  }

  /**
   * if there is no error, the form is filled with the current model values.
   * After that, the form is rendered.
	 * @param context context
	 * @return document
   */
  public Document render(RequestContext context) throws Exception {
    if (dirty || !haveError)
      revert(context);
    return super.render(context);
  }

  /**
   * Sets the bean.
   * @param bean The bean to set
   */
  public void setBean(Object bean) {
    this.bean = bean;
    this.dirty = true;
  }

  /**
   * Returns the beanModel.
   * @return Object
   */
  public Object getBean() {
    return bean;
  }

  /**
   * sets an error message to a DOM element
   *
   * @param id the value of the id attribute of the
   * DOM Element that the error message will be attached to
   *
   * @param message the error message or null to remove the error attribute
   */
  public void setError(String id, String message) {
    Element elem = DomUtils.findElementWithId(id, getDocument().getDocumentElement());
    if (elem == null) {
      logger.error("No errorElement found with id=" + id + " in XML form");
      return;
    }
    if (message == null)
      DomUtils.removeAttribute(elem, "error");
    else
      elem.setAttribute("error", message);
    haveError = true;
  }

  /**
   * clears all error attributes in the DOM
   */
  public void clearErrors() {
    clearErrors(getDocument().getChildNodes());
    haveError = false;
  }

  protected void clearErrors(NodeList list) {
    int len = list.getLength();
    for (int i = 0; i < len; i++) {
      Node n = list.item(i);
      if (n.getNodeType() != Node.ELEMENT_NODE)
        continue;
      Element x = (Element) list.item(i);
      DomUtils.removeAttribute(x, "error");
      clearErrors(x.getChildNodes());
    }
  }

  /**
   * handler for an actionReference (e.g. a Button).
   */
  private class ActionReferenceListener implements RequestListener {
    private Element elem;
    private String methodPath;

    public ActionReferenceListener(Element elem, String methodPath) {
      this.elem = elem;
      this.methodPath = methodPath;
    }

    public void request(RequestContext context) throws Exception {
      // validate or revert?
      String action = elem.getAttribute("action");
      if (action.equals("revert"))
        revert(context);
      else if (action.equals("validate")) {
        if (!validate(context))
          return;
      }

      // to be compatible with previous WCF versions the state must be modified
      // before the actionReference is invoked.
      Object[] state = performButtonActions(context);
      if (!invokeActionReference(context)) {
        restoreButtonActions(context, state);
        return;
      }

      wizardPageSupport.fireWizardButton(context, methodName());
    }

    private String methodName() {
      String methodName = methodPath;
      int index = methodPath.lastIndexOf('.');
      if (index > 0)
        methodName = methodPath.substring(index + 1);
      return methodName;
    }

    private Object beanTarget() throws IllegalAccessException, InvocationTargetException,
        NoSuchMethodException {
      Object target = bean;
      String methodName = methodPath;
      int index = methodPath.lastIndexOf('.');
      if (index > 0) {
        String beanPath = methodPath.substring(0, index);
        target = PropertyUtils.getProperty(target, beanPath);
      }
      return target;
    }

    /**
     * assumes that the actionReference will not throw an ActionReferenceException
     */
    private Object[] performButtonActions(RequestContext context) {
      Object[] state = new Object[2];
      // hide?
      state[0] = Boolean.valueOf(isVisible());
      if ("true".equals(elem.getAttribute("hide")))
        setVisible(false);
      // forward?
      state[1] = getNextView();
      String forward = elem.getAttribute("forward");
      if (forward != null && forward.length() > 0)
        setNextView(forward);
      // set attribute?
      String successAttr = elem.getAttribute("successAttr");
      if (successAttr.length() > 0)
        context.getRequest().setAttribute(successAttr, "true");
      return state;
    }

    /**
     * restores state after actionRefrence threw an ActionReferenceException
     */
    private void restoreButtonActions(RequestContext context, Object[] state) {
      setVisible(((Boolean) state[0]).booleanValue());
      setNextView((String) state[1]);
      String successAttr = elem.getAttribute("successAttr");
      if (successAttr.length() > 0)
        context.getRequest().removeAttribute(successAttr);
    }

    /**
     * invokes the actionReference on the bean.
     * @return false, if the actionReference threw an ActionReferenceException
     */
    private boolean invokeActionReference(RequestContext context) throws IllegalAccessException,
        InvocationTargetException {
      if (bean == null)
        return true;
      try {
        Object target = beanTarget();
        String methodName = methodName();
        Class c = target.getClass();
        Method m = c.getMethod(methodName, new Class[] { RequestContext.class});
        m.invoke(target, new Object[] { context});
        return true;
      } catch (NoSuchMethodException e) {
        logger.error("method not found: " + methodPath + " in " + bean);
        return true;
      } catch (InvocationTargetException e) {
        Throwable t = e.getTargetException();
        if (t instanceof ActionReferenceException)
          return false;
        throw e;
      }
    }
  }

  /**
   * finds all DOM elements with an actionReference attribute
   * and adds a Requesthandler for these.
   */
  private void initActionReferences() {
    try {
      DOMXPath dx = new DOMXPath("//*[@actionReference]");
	  List elements = NoWarn.castList(dx.selectNodes(getDocument()));
      for (Element elem : elements) {
        String methodPath = elem.getAttribute("actionReference");
        if ("none".equals(methodPath))
          continue;
        RequestListener rl = new ActionReferenceListener(elem, methodPath);
        String id = elem.getAttribute("id");
        getDispatcher().addRequestListener(id, null, rl);
      }
    } catch (JaxenException e) {
      logger.error(null, e);
    }
  }

  /**
   * adds all editable properties to the bookmark state. Editable
   * properties are addressed via the modelReference
   * attribute in the DOM.
	 * @param levelOfDetail level of detail
	 * @return map
   */
  public Object retrieveBookmarkState(int levelOfDetail) {
    if (!bookmarkable)
      return null;
	
	@SuppressWarnings("unchecked")
    Map map = (Map) super.retrieveBookmarkState(levelOfDetail);
    try {
      DOMXPath dx = new DOMXPath("//*[@modelReference]");
	  List elements = NoWarn.castList(dx.selectNodes(getDocument()));
      for (Element elem : elements) {
        if ("false".equals(elem.getAttribute("bookmark")))
          continue;
        String ref = XoplonCtrl.getModelReference(elem);
        Object value = PropertyUtils.getProperty(bean, ref);
        map.put(ref, value);
      }
    } catch (JaxenException e) {
      logger.error("?", e);
    } catch (IllegalAccessException e) {
      logger.error("?", e);
    } catch (InvocationTargetException e) {
      logger.error("?", e);
    } catch (NoSuchMethodException e) {
      logger.error("?", e);
    }
    return map;
  }

  /**
   * restores all editable properties from the bookmark state. Editable
   * properties are addressed via the modelReference
   * attribute in the DOM.
   */
  public void setBookmarkState(Object state) {
    if (!bookmarkable)
      return;
    if (!(state instanceof Map))
      return;
    super.setBookmarkState(state);
	@SuppressWarnings("unchecked")
    Map map = (Map) state;
    try {
      DOMXPath dx = new DOMXPath("//*[@modelReference]");
	  List elements = NoWarn.castList(dx.selectNodes(getDocument()));
      for (Element elem : elements) {
        if ("false".equals(elem.getAttribute("bookmark")))
          continue;
        // Properties may be added and removed over time.
        // So we react gentle if setting of a property fails
        try {
          String ref = XoplonCtrl.getModelReference(elem);
          Object value = map.get(ref);
          if (value != null)
            PropertyUtils.setProperty(bean, ref, value);
        } catch (IllegalAccessException e1) {
          logger.error(null, e1);
        } catch (InvocationTargetException e1) {
          logger.error(null, e1);
          logger.error(null, e1.getTargetException());
        } catch (NoSuchMethodException e1) {
          logger.warn(null, e1);
        }
      }
    } catch (JaxenException e) {
      logger.error(null, e);
    }
  }

  /**
   * @return is bookmarkable
   */
  public boolean isBookmarkable() {
    return bookmarkable;
  }

  /**
   * @param b bookmarkable
   */
  public void setBookmarkable(boolean b) {
    bookmarkable = b;
  }

  public void addPageListener(PageListener l) {
    wizardPageSupport.addPageListener(l);
  }

  public void removePageListener(PageListener l) {
    wizardPageSupport.removePageListener(l);
  }

  /**
   * shows/hides form buttons automatically depending on the position of 
   * this form in a wizard.
   * This method sets the buttons with the following ids:
   * $id.back, $id.next, $id.cancel, $id.finish, $id.ok. 
   * 
    *
  • Only page: ok/cancel *
  • First page: next/cancel/finish *
  • Intermediate page: back/next/cancel/finish *
  • Last page: back/cancel/finish *
* Finish button is only displayed if the form component * supports this: supportsPrematureFinishBtn() * @param pagePos page position */ public void pageAdded(WizardPagePosition pagePos) { boolean isBack = false; boolean isNext = false; boolean isFinish = false; boolean isOk = false; boolean isCancel = true; if (pagePos == WizardPagePosition.FIRST_PAGE) { isNext = true; isFinish = isFinishButton(); } else if (pagePos == WizardPagePosition.MIDDLE_PAGE) { isNext = true; isBack = true; isFinish = isFinishButton(); } else if (pagePos == WizardPagePosition.LAST_PAGE) { isNext = false; isBack = true; isFinish = true; } else if (pagePos == WizardPagePosition.SINGLE_PAGE) { isOk = true; } showButton("back", isBack); showButton("next", isNext); showButton("finish", isFinish); showButton("ok", isOk); showButton("cancel", isCancel); } private void showButton(String name, boolean show) { Element btn = getElement(getId() + "." + name); if (btn == null) return; if(XoplonNS.getAttribute(btn, "hidden")!=null) XoplonNS.removeAttribute(btn, "hidden"); if (!show) XoplonNS.setAttribute(btn, "hidden", "true"); } /** * is finish button * @return is finish button */ public boolean isFinishButton() { return finishButton; } /** * is finish button * @param finishButton is finish button */ public void setFinishButton(boolean finishButton) { this.finishButton = finishButton; } /** * page skipped */ public void pageSkipped() { } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy