blob: fcc456dda61f84da0856deb1a91be156a32b804d [file] [log] [blame]
/*
* $Id$
*
* Copyright 2001-2004 The Apache Software Foundation.
*
* Licensed 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.scaffold;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.validator.DynaValidatorForm;
import org.apache.struts.Globals;
import org.apache.commons.scaffold.lang.ChainedException;
import org.apache.commons.scaffold.lang.Log;
import org.apache.commons.scaffold.lang.Tokens;
import org.apache.commons.scaffold.text.ConvertUtils;
/**
* Enhanced base ActionForm.
* @version $Rev$ $Date$
*/
public class BaseForm extends DynaValidatorForm {
// ---------------------------------------------------------- Remote Host
/**
* The network address making this request.
* <p>
* This is the value returned by reqest.getremoteHost.
* It is provided so that this can be logged by components on
* the business tier if needed.
* This property is maintained automatically through the
* <code>reset</code> method.
*/
private String remoteHost = null;
/**
* Return our remoteHost property.
*/
public String getRemoteHost() {
return (this.remoteHost);
}
/**
* Set our remoteHost property.
*/
public void setRemoteHost(String remoteHost) {
this.remoteHost = remoteHost;
}
/**
* Sets RemoteHost attribute to request.getRemoteHost().
*/
protected void resetRemoteHost(HttpServletRequest request) {
setRemoteHost(request.getRemoteHost());
}
// ------------------------------------------------------- Session Locale
/**
* The session attribute key for our session locale [Action.LOCALE_KEY].
* (Suggestion only, may be overridden by presentation framework
*/
public static String STRUTS_LOCALE_KEY = Globals.LOCALE_KEY;
/**
* Our locale property.
* <p>
* If the formBean instance is mutable, this is set to the Struts
* session locale whenever <code>reset()</code> is called. to update
* the session locale, use <code>putSessionLocale()</code>.
* <p>
* The properties refer to this as the "SessionLocale" so as to avoid
* naming/signature conflicts with business beans which may also
* maintain a Locale property.
*/
private Locale locale = null;
/**
* Set our locale property.
*
*/
public void setSessionLocale(Locale locale) {
this.locale = locale;
}
/**
* Retrieve our locale property.
*/
public Locale getSessionLocale() {
return this.locale;
}
/**
* Return the session key attribute for our locale object.
*/
public String getSessionLocaleName() {
return STRUTS_LOCALE_KEY;
}
/**
* Reset our locale property to the locale object found in
* the session associated with this request.
*/
protected void resetSessionLocale(HttpServletRequest request) {
HttpSession session = request.getSession();
if (session!=null) {
setSessionLocale((Locale)
session.getAttribute(getSessionLocaleName()));
}
else {
setSessionLocale(Locale.getDefault());
}
} // end resetSessionLocale
/**
* Change the locale property in the session to our locale object,
* or the default Locale if ours is null.
*/
protected void putSessionLocale(HttpServletRequest request) {
Locale locale = getSessionLocale();
if (null==locale) locale = Locale.getDefault();
request.getSession(true).setAttribute(Globals.LOCALE_KEY,locale);
} // end putSessionLocale
/**
* Display the user's locale setting or the default locale.
*/
public String getLocaleDisplay() {
Locale locale = getSessionLocale();
if (null==locale) locale = Locale.getDefault();
return locale.getDisplayName();
} // end getLocaleDisplay
/**
* Set our locale to given ISO Language Code.
* An empty String is used for the country.
* <p>
* Mainly provided for completeness.
*/
public void setLocaleDisplay(String language) {
setSessionLocale(new Locale(language,EMPTY));
}
// -------------------------------------------------------------- Mutable
/**
* The mutable state.
* <p>
* To avoid autopopulation when forwarding beans between actions,
* set mutable to be true and be sure all setters observe the
* mutable state.
* <p>
* (<code>if (isMutable()) this.field = field;</code>).
* subject to autopopulation.
*/
private boolean mutable = true;
/**
* Set the mutable state.
*/
public void setMutable(boolean mutable) {
this.mutable = mutable;
}
/**
* Retrieve the mutable state.
*/
public boolean isMutable() {
return this.mutable;
}
// ------------------------------------------------------------- Dispatch
/**
* The dispatch property.
* <p>
* This can be set by a JavaScript buttonto indicate which task should
* be performed (or dispatched) by an action.
* Observes the bean's mutable state.
*/
public String dispatch = null;
/**
* Set dispatch.
*/
public void setDispatch(String dispatch) {
if (isMutable()) this.dispatch = dispatch;
}
/**
* Get the dispatch.
*/
public String getDispatch() {
return this.dispatch;
}
// --------------------------------------------------------- Public Methods
/**
* A static, empty String used by isBlank.
*/
private static String EMPTY = "";
/**
* Convenience method to check for a null or empty String.
*
* @param s The sting to check
*/
protected boolean blank(String s) {
return ConvertUtils.blank(s);
}
/**
* Convenience method to check for a null, empty, or "0" String.
*
* @param s The sting to check
*/
protected boolean blankValue(String s) {
return ConvertUtils.blankValue(s);
}
/**
* @deprecated Use blank instead.
*/
protected boolean isBlank(String s) {
return blank(s);
}
/**
* Convenience method to test for a required field
* and setup the error message.
*/
protected void required(
ActionErrors errors,
String field,
String name,
String arg) {
if ((null==field) || (0==field.length())) {
errors.add(name,
new ActionError(Tokens.ERRORS_REQUIRED,arg));
}
}
/**
* Convenience method to test for a required array
* and setup the error message.
*/
protected void required(
ActionErrors errors,
String[] field,
String name,
String arg) {
if ((null==field) || (0==field.length)) {
errors.add(name,
new ActionError(Tokens.ERRORS_REQUIRED,arg));
}
}
/**
* Create an object of the specified class,
* throwing a runtime exception if any error occurs.
* If an exception is not thrown, then helper is guaranteed to exist.
*
* @param objectClass The name of the class
* @throws IllegalArgumentException if object cannot be
* instantiated
*/
public Object createObject(
String objectClass) {
// Try the create
Object object = null;
try {
object = Class.forName(objectClass).newInstance();
}
catch (Throwable t) {
object = null;
// assemble message: {class}: {exception}
StringBuffer sb = new StringBuffer();
sb.append(Log.CREATE_OBJECT_ERROR);
sb.append(Log.CLASS);
sb.append(objectClass);
sb.append(Log.SPACE);
sb.append(Log.ACTION_EXCEPTION);
sb.append(t.toString());
// throw runtime exception
throw new IllegalArgumentException(sb.toString());
}
return object;
} // createHelperObject()
/**
* Return map of properties for this bean.
* Base method uses <code>PropertyUtils.describe</code>.
* Override if some properties should not be transfered
* this way, or a property name should be altered.
* This will return the actual public properties.
*
* @exception Exception on any error.
*/
public Map describe() throws Exception {
try {
return PropertyUtils.describe(this);
} catch (Throwable t) {
throw new ChainedException(t);
}
} // end describe
/**
* Set properties from given object.
* Base method uses <code>BeanUtils.copyProperties</code> and
* <code>PropertyUtils.describe</code>.
*
* @param o The object to use to populate this object.
* @exception Exception on any error.
*/
public void set(Object o) throws Exception {
try {
BeanUtils.copyProperties(this,o);
} catch (Throwable t) {
throw new ChainedException(t);
}
} // end set
/**
* Populate matching properties on given object.
* Base method uses <code>BeanUtils.copyProperties</code> and
* <code>describe()</code>.
*
* @param o The object to populate from this object.
* @exception Exception on any error.
*/
public void populate(Object o) throws Exception {
try {
BeanUtils.copyProperties(o,describe());
} catch (Throwable t) {
throw new ChainedException(t);
}
} // end populate
/**
* // :FIXME: Needs testing. Works OK without a profile bean =:o)
* Merge a profile bean with this bean to provide a unified map
* of the combined parameters. Will on add properties to the map
* from the profile bean if matching property on this bean is
* blank or null (which includes absent).
* The result is a union of the properties, with the this
* bean's non-blank properties having precedence over the
* profile's properties. The profile is a base that this bean
* can override on the fly -- If this bean does not supply a
* property, then the profile property is used. But any
* property named on the userProfile is included (even if
* it has no match on this bean).
* <p>
* If profile is null, a map of this bean's properties is returned.
* <p>
* The profile can be any JavaBean.
* <p>
* This method is forwardly-compatible with BaseMapForm.
* For an instance of BaseMapForm, getMap() is used; otherwise
* describe() or PropertyUtils.describe() is used.
*
* :FIXME: Needs testing. Works OK without a profile bean =:o)
* @param profile The profile bean, if any
* @throws Exception if error transfering data to map
*/
protected Map merge(Object profile) throws Exception {
Map formMap = null;
if (this instanceof BaseMapForm) {
BaseMapForm form = (BaseMapForm) this;
formMap = form.getMap();
}
else {
formMap = describe();
}
if (profile!=null) {
Map userMap = null;
if (profile instanceof BaseMapForm) {
BaseMapForm form = (BaseMapForm) profile;
userMap = form.getMap();
}
else if (profile instanceof BaseForm) {
BaseForm form = (BaseForm) profile;
userMap = form.describe();
}
else {
userMap = PropertyUtils.describe(this);
}
// Add user element to formMap if form element is null or blank
// Starting with the formMap, for every element in the userMap,
// see if the formMap element is non-existant, null, or an empty String.
// If it is (our formMap doesn't override), add the userMap value
// to the formMap.
Iterator i = userMap.keySet().iterator();
while (i.hasNext()) {
String key = (String) i.next();
String formValue = (String) formMap.get(key);
if (isBlank(formValue)) {
formMap.put(key,userMap.get(key));
}
}
}
return formMap;
} // end merge
/**
* If bean is set to mutable, calls <code>resetSessionLocale</code>
* and <code>resetRemoteHost</code>.
*
* Subclasses resetting their own fields should observe the mutable
* state (<code>if (isMutable()) ...</code>).
*
* @param mapping The mapping used to select this instance
* @param request The servlet request we are processing
*/
public void reset(
ActionMapping mapping,
HttpServletRequest request) {
if (isMutable()) {
super.reset(mapping,request);
// :TODO: Might be useful to have a collection of reset listeners
resetRemoteHost(request);
resetSessionLocale(request);
}
} // end reset
/**
* Return an empty ActionErrors or the result of calling
* the superclass validate. Will not return null.
*/
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request) {
ActionErrors errors = super.validate(mapping,request);
if (null==errors) errors = new ActionErrors();
return errors;
}
} // end BaseForm