/*
* $Id: FormBeanConfig.java 472728 2006-11-09 01:10:58Z niallp $
*
* 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.struts.config;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.MutableDynaClass;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.action.DynaActionForm;
import org.apache.struts.action.DynaActionFormClass;
import org.apache.struts.chain.commands.util.ClassUtils;
import org.apache.struts.chain.contexts.ActionContext;
import org.apache.struts.chain.contexts.ServletActionContext;
import org.apache.struts.util.RequestUtils;
import org.apache.struts.validator.BeanValidatorForm;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
/**
* <p>A JavaBean representing the configuration information of a
* <code><form-bean></code> element in a Struts configuration file.<p>
*
* @version $Rev: 472728 $ $Date: 2006-01-17 07:26:20 -0500 (Tue, 17 Jan 2006)
* $
* @since Struts 1.1
*/
public class FormBeanConfig extends BaseConfig {
private static final Log log = LogFactory.getLog(FormBeanConfig.class);
// ----------------------------------------------------- Instance Variables
/**
* The set of FormProperty elements defining dynamic form properties for
* this form bean, keyed by property name.
*/
protected HashMap formProperties = new HashMap();
/**
* <p>The lockable object we can synchronize on when creating
* DynaActionFormClass.</p>
*/
protected String lock = "";
// ------------------------------------------------------------- Properties
/**
* The DynaActionFormClass associated with a DynaActionForm.
*/
protected transient DynaActionFormClass dynaActionFormClass;
/**
* Is the form bean class an instance of DynaActionForm with dynamic
* properties?
*/
protected boolean dynamic = false;
/**
* The name of the FormBeanConfig that this config inherits configuration
* information from.
*/
protected String inherit = null;
/**
* Have the inheritance values for this class been applied?
*/
protected boolean extensionProcessed = false;
/**
* The unique identifier of this form bean, which is used to reference
* this bean in <code>ActionMapping</code> instances as well as for the
* name of the request or session attribute under which the corresponding
* form bean instance is created or accessed.
*/
protected String name = null;
/**
* The fully qualified Java class name of the implementation class to be
* used or generated.
*/
protected String type = null;
/**
* Is this DynaClass currently restricted (for DynaBeans with a
* MutableDynaClass).
*/
protected boolean restricted = false;
/**
* <p>Return the DynaActionFormClass associated with a
* DynaActionForm.</p>
*
* @throws IllegalArgumentException if the ActionForm is not dynamic
*/
public DynaActionFormClass getDynaActionFormClass() {
if (dynamic == false) {
throw new IllegalArgumentException("ActionForm is not dynamic");
}
synchronized (lock) {
if (dynaActionFormClass == null) {
dynaActionFormClass = new DynaActionFormClass(this);
}
}
return dynaActionFormClass;
}
public boolean getDynamic() {
return (this.dynamic);
}
public String getExtends() {
return (this.inherit);
}
public void setExtends(String extend) {
throwIfConfigured();
this.inherit = extend;
}
public boolean isExtensionProcessed() {
return extensionProcessed;
}
public String getName() {
return (this.name);
}
public void setName(String name) {
throwIfConfigured();
this.name = name;
}
public String getType() {
return (this.type);
}
public void setType(String type) {
throwIfConfigured();
this.type = type;
Class dynaBeanClass = DynaActionForm.class;
Class formBeanClass = formBeanClass();
if (formBeanClass != null) {
if (dynaBeanClass.isAssignableFrom(formBeanClass)) {
this.dynamic = true;
} else {
this.dynamic = false;
}
} else {
this.dynamic = false;
}
}
/**
* <p>Indicates whether a MutableDynaClass is currently restricted.</p>
* <p>If so, no changes to the existing registration of property names,
* data types, readability, or writeability are allowed.</p>
*/
public boolean isRestricted() {
return restricted;
}
/**
* <p>Set whether a MutableDynaClass is currently restricted.</p> <p>If
* so, no changes to the existing registration of property names, data
* types, readability, or writeability are allowed.</p>
*/
public void setRestricted(boolean restricted) {
this.restricted = restricted;
}
// ------------------------------------------------------ Protected Methods
/**
* <p>Traces the hierarchy of this object to check if any of the ancestors
* is extending this instance.</p>
*
* @param moduleConfig The configuration for the module being configured.
* @return true if circular inheritance was detected.
*/
protected boolean checkCircularInheritance(ModuleConfig moduleConfig) {
String ancestorName = getExtends();
while (ancestorName != null) {
// check if we have the same name as an ancestor
if (getName().equals(ancestorName)) {
return true;
}
// get our ancestor's ancestor
FormBeanConfig ancestor =
moduleConfig.findFormBeanConfig(ancestorName);
ancestorName = ancestor.getExtends();
}
return false;
}
/**
* <p>Compare the form properties of this bean with that of the given and
* copy those that are not present.</p>
*
* @param config The form bean config to copy properties from.
* @see #inheritFrom(FormBeanConfig)
*/
protected void inheritFormProperties(FormBeanConfig config)
throws ClassNotFoundException, IllegalAccessException,
InstantiationException, InvocationTargetException {
throwIfConfigured();
// Inherit form property configs
FormPropertyConfig[] baseFpcs = config.findFormPropertyConfigs();
for (int i = 0; i < baseFpcs.length; i++) {
FormPropertyConfig baseFpc = baseFpcs[i];
// Do we have this prop?
FormPropertyConfig prop =
this.findFormPropertyConfig(baseFpc.getName());
if (prop == null) {
// We don't have this, so let's copy it
prop =
(FormPropertyConfig) RequestUtils.applicationInstance(baseFpc.getClass()
.getName());
BeanUtils.copyProperties(prop, baseFpc);
this.addFormPropertyConfig(prop);
prop.setProperties(baseFpc.copyProperties());
}
}
}
// --------------------------------------------------------- Public Methods
/**
* <p>Create and return an <code>ActionForm</code> instance appropriate to
* the information in this <code>FormBeanConfig</code>.</p>
*
* <p>Although this method is not formally deprecated yet, where possible,
* the form which accepts an <code>ActionContext</code> as an argument is
* preferred, to help sever direct dependencies on the Servlet API. As
* the ActionContext becomes more familiar in Struts, this method will
* almost certainly be deprecated.</p>
*
* @param servlet The action servlet
* @return ActionForm instance
* @throws IllegalAccessException if the Class or the appropriate
* constructor is not accessible
* @throws InstantiationException if this Class represents an abstract
* class, an array class, a primitive type,
* or void; or if instantiation fails for
* some other reason
*/
public ActionForm createActionForm(ActionServlet servlet)
throws IllegalAccessException, InstantiationException {
Object obj = null;
// Create a new form bean instance
if (getDynamic()) {
obj = getDynaActionFormClass().newInstance();
} else {
obj = formBeanClass().newInstance();
}
ActionForm form = null;
if (obj instanceof ActionForm) {
form = (ActionForm) obj;
} else {
form = new BeanValidatorForm(obj);
}
form.setServlet(servlet);
if (form instanceof DynaBean
&& ((DynaBean) form).getDynaClass() instanceof MutableDynaClass) {
DynaBean dynaBean = (DynaBean) form;
MutableDynaClass dynaClass =
(MutableDynaClass) dynaBean.getDynaClass();
// Add properties
dynaClass.setRestricted(false);
FormPropertyConfig[] props = findFormPropertyConfigs();
for (int i = 0; i < props.length; i++) {
dynaClass.add(props[i].getName(), props[i].getTypeClass());
dynaBean.set(props[i].getName(), props[i].initial());
}
dynaClass.setRestricted(isRestricted());
}
if (form instanceof BeanValidatorForm) {
((BeanValidatorForm)form).initialize(this);
}
return form;
}
/**
* <p>Create and return an <code>ActionForm</code> instance appropriate to
* the information in this <code>FormBeanConfig</code>.</p>
* <p><b>NOTE:</b> If the given <code>ActionContext</code> is not of type
* <code>ServletActionContext</code> (or a subclass), then the form which
* is returned will have a null <code>servlet</code> property. Some of
* the subclasses of <code>ActionForm</code> included in Struts will later
* throw a <code>NullPointerException</code> in this case. </p> <p>TODO:
* Find a way to control this direct dependency on the Servlet API.</p>
*
* @param context The ActionContext.
* @return ActionForm instance
* @throws IllegalAccessException if the Class or the appropriate
* constructor is not accessible
* @throws InstantiationException if this Class represents an abstract
* class, an array class, a primitive type,
* or void; or if instantiation fails for
* some other reason
*/
public ActionForm createActionForm(ActionContext context)
throws IllegalAccessException, InstantiationException {
ActionServlet actionServlet = null;
if (context instanceof ServletActionContext) {
ServletActionContext saContext = (ServletActionContext) context;
actionServlet = saContext.getActionServlet();
}
return createActionForm(actionServlet);
}
/**
* <p>Checks if the given <code>ActionForm</code> instance is suitable for
* use as an alternative to calling this <code>FormBeanConfig</code>
* instance's <code>createActionForm</code> method.</p>
*
* @param form an existing form instance that may be reused.
* @return true if the given form can be reused as the form for this
* config.
*/
public boolean canReuse(ActionForm form) {
if (form != null) {
if (this.getDynamic()) {
String className = ((DynaBean) form).getDynaClass().getName();
if (className.equals(this.getName())) {
log.debug("Can reuse existing instance (dynamic)");
return (true);
}
} else {
try {
// check if the form's class is compatible with the class
// we're configured for
Class formClass = form.getClass();
if (form instanceof BeanValidatorForm) {
BeanValidatorForm beanValidatorForm =
(BeanValidatorForm) form;
if (beanValidatorForm.getInstance() instanceof DynaBean) {
String formName = beanValidatorForm.getStrutsConfigFormName();
if (getName().equals(formName)) {
log.debug("Can reuse existing instance (BeanValidatorForm)");
return true;
} else {
return false;
}
}
formClass = beanValidatorForm.getInstance().getClass();
}
Class configClass =
ClassUtils.getApplicationClass(this.getType());
if (configClass.isAssignableFrom(formClass)) {
log.debug("Can reuse existing instance (non-dynamic)");
return (true);
}
} catch (Exception e) {
log.debug("Error testing existing instance for reusability; just create a new instance",
e);
}
}
}
return false;
}
/**
* Add a new <code>FormPropertyConfig</code> instance to the set
* associated with this module.
*
* @param config The new configuration instance to be added
* @throws IllegalArgumentException if this property name has already been
* defined
*/
public void addFormPropertyConfig(FormPropertyConfig config) {
throwIfConfigured();
if (formProperties.containsKey(config.getName())) {
throw new IllegalArgumentException("Property " + config.getName()
+ " already defined");
}
formProperties.put(config.getName(), config);
}
/**
* Return the form property configuration for the specified property name,
* if any; otherwise return <code>null</code>.
*
* @param name Form property name to find a configuration for
*/
public FormPropertyConfig findFormPropertyConfig(String name) {
return ((FormPropertyConfig) formProperties.get(name));
}
/**
* Return the form property configurations for this module. If there are
* none, a zero-length array is returned.
*/
public FormPropertyConfig[] findFormPropertyConfigs() {
FormPropertyConfig[] results =
new FormPropertyConfig[formProperties.size()];
return ((FormPropertyConfig[]) formProperties.values().toArray(results));
}
/**
* Freeze the configuration of this component.
*/
public void freeze() {
super.freeze();
FormPropertyConfig[] fpconfigs = findFormPropertyConfigs();
for (int i = 0; i < fpconfigs.length; i++) {
fpconfigs[i].freeze();
}
}
/**
* <p>Inherit values that have not been overridden from the provided
* config object. Subclasses overriding this method should verify that
* the given parameter is of a class that contains a property it is trying
* to inherit:</p>
*
* <pre>
* if (config instanceof MyCustomConfig) {
* MyCustomConfig myConfig =
* (MyCustomConfig) config;
*
* if (getMyCustomProp() == null) {
* setMyCustomProp(myConfig.getMyCustomProp());
* }
* }
* </pre>
*
* <p>If the given <code>config</code> is extending another object, those
* extensions should be resolved before it's used as a parameter to this
* method.</p>
*
* @param config The object that this instance will be inheriting its
* values from.
* @see #processExtends(ModuleConfig)
*/
public void inheritFrom(FormBeanConfig config)
throws ClassNotFoundException, IllegalAccessException,
InstantiationException, InvocationTargetException {
throwIfConfigured();
// Inherit values that have not been overridden
if (getName() == null) {
setName(config.getName());
}
if (!isRestricted()) {
setRestricted(config.isRestricted());
}
if (getType() == null) {
setType(config.getType());
}
inheritFormProperties(config);
inheritProperties(config);
}
/**
* <p>Inherit configuration information from the FormBeanConfig that this
* instance is extending. This method verifies that any form bean config
* object that it inherits from has also had its processExtends() method
* called.</p>
*
* @param moduleConfig The {@link ModuleConfig} that this bean is from.
* @see #inheritFrom(FormBeanConfig)
*/
public void processExtends(ModuleConfig moduleConfig)
throws ClassNotFoundException, IllegalAccessException,
InstantiationException, InvocationTargetException {
if (configured) {
throw new IllegalStateException("Configuration is frozen");
}
String ancestor = getExtends();
if ((!extensionProcessed) && (ancestor != null)) {
FormBeanConfig baseConfig =
moduleConfig.findFormBeanConfig(ancestor);
if (baseConfig == null) {
throw new NullPointerException("Unable to find "
+ "form bean '" + ancestor + "' to extend.");
}
// Check against circule inheritance and make sure the base config's
// own extends have been processed already
if (checkCircularInheritance(moduleConfig)) {
throw new IllegalArgumentException(
"Circular inheritance detected for form bean " + getName());
}
// Make sure the ancestor's own extension has been processed.
if (!baseConfig.isExtensionProcessed()) {
baseConfig.processExtends(moduleConfig);
}
// Copy values from the base config
inheritFrom(baseConfig);
}
extensionProcessed = true;
}
/**
* Remove the specified form property configuration instance.
*
* @param config FormPropertyConfig instance to be removed
*/
public void removeFormPropertyConfig(FormPropertyConfig config) {
if (configured) {
throw new IllegalStateException("Configuration is frozen");
}
formProperties.remove(config.getName());
}
/**
* Return a String representation of this object.
*/
public String toString() {
StringBuffer sb = new StringBuffer("FormBeanConfig[");
sb.append("name=");
sb.append(this.name);
sb.append(",type=");
sb.append(this.type);
sb.append(",extends=");
sb.append(this.inherit);
sb.append("]");
return (sb.toString());
}
// ------------------------------------------------------ Protected Methods
/**
* Return the <code>Class</code> instance for the form bean implementation
* configured by this <code>FormBeanConfig</code> instance. This method
* uses the same algorithm as <code>RequestUtils.applicationClass()</code>
* but is reproduced to avoid a runtime dependence.
*/
protected Class formBeanClass() {
ClassLoader classLoader =
Thread.currentThread().getContextClassLoader();
if (classLoader == null) {
classLoader = this.getClass().getClassLoader();
}
try {
return (classLoader.loadClass(getType()));
} catch (Exception e) {
return (null);
}
}
}