| /* |
| * $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 |