"); + /** + * Handler method which will handle a request for a resource in the web application and stream it + * back to the client inside of an HTML preformatted section. + */ + public Resolution view() { + final InputStream stream = + getContext() + .getRequest() + .getSession() + .getServletContext() + .getResourceAsStream(this.resource); + final BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); - String line; - while ( (line = reader.readLine()) != null ) { - writer.write(HtmlUtil.encode(line)); - writer.write("\n"); - } + return new Resolution() { + public void execute(HttpServletRequest request, HttpServletResponse response) + throws Exception { + PrintWriter writer = response.getWriter(); + writer.write(""); + } + }; + } - Iterator"); + writer.write(resource); + writer.write(" "); - writer.write(""); - } - }; - } + String line; + while ((line = reader.readLine()) != null) { + writer.write(HtmlUtil.encode(line)); + writer.write("\n"); + } - /** - * Method used when this ActionBean is used as a view helper. Returns a listing of all the - * JSPs and ActionBeans available for viewing. - */ - @SuppressWarnings("unchecked") - public Collection getAvailableResources() { - ServletContext ctx = getContext().getRequest().getSession().getServletContext(); - SortedSetresources = new TreeSet (); - resources.addAll( ctx.getResourcePaths("/bugzooky/")); - resources.addAll( ctx.getResourcePaths("/bugzooky/layout/")); - resources.addAll( ctx.getResourcePaths("/WEB-INF/src/")); + writer.write("
Please provide the following information:
Interface for all classes that respond to user interface events. Implementations receive - * information about the event (usually a form submission) in two ways. The first is through a - * ActionBeanContext object which will always be set on the ActionBean prior to any user - * implemented "handler" methods being invoked. The second is through the setting of - * JavaBean style properties on the object (and nested JavaBeans). Values submitted in an HTML - * Form will be bound to a property on the object if such a property is defined. The - * ActionBeanContext is used primarily to provide access to Servlet APIs such as the request and - * response, and other information generated during the pre-processing of the request.
+ * Interface for all classes that respond to user interface events. Implementations receive + * information about the event (usually a form submission) in two ways. The first is through a + * ActionBeanContext object which will always be set on the ActionBean prior to any user implemented + * "handler" methods being invoked. The second is through the setting of JavaBean style + * properties on the object (and nested JavaBeans). Values submitted in an HTML Form will be bound + * to a property on the object if such a property is defined. The ActionBeanContext is used + * primarily to provide access to Servlet APIs such as the request and response, and other + * information generated during the pre-processing of the request. * *How Stripes determines which method to invoke is based on Annotations as opposed to a strict - * interface. Firstly, each ActionBean should be annotated with a UrlBinding which specifies the - * path within the web application that the ActionBean will be bound to. E.g:
+ * interface. Firstly, each ActionBean should be annotated with a UrlBinding which specifies the + * path within the web application that the ActionBean will be bound to. E.g: * ** @UrlBinding("/action/SampleAction") @@ -38,12 +38,12 @@ * *At run time Stripes will discover all implementations of ActionBean and, when a form is * submitted, will locate the ActionBean to use by matching the path in the request to the - * UrlBinding annotation on the ActionBean.
+ * UrlBinding annotation on the ActionBean. * - *The way in which a specific event is mapped to a "handler" method can vary. The + *
The way in which a specific event is mapped to a "handler" method can vary. The * default method used by Stripes is to identify the name of the button or image button * used to submit the request, and to find the handler method that can handle that event. The way - * this is declared in the ActionBean is like this:
+ * this is declared in the ActionBean is like this: * ** @HandlesEvent("SampleEvent") @@ -54,17 +54,17 @@ ** *Event names specified in the HandlesEvent annotation should be unique within a given - * ActionBean (and it's superclasses), but can be re-used across ActionBeans. For example, a given + * ActionBean (and it's superclasses), but can be re-used across ActionBeans. For example, a given * ActionBean should not have two methods with the annotation @HandlesEvent("Update"), * but it would be perfectly reasonable to have two different ActionBeans handle events, from - * different forms, called Update.
+ * different forms, called Update. * *It is also possible to designate a method as the default method for handling events in the - * case that Stripes cannot figure out a specific handler method to invoke. This occurs most often + * case that Stripes cannot figure out a specific handler method to invoke. This occurs most often * when a form is submitted by the user hitting the enter/return key, and so no form button is - * activated. Essentially the default handler is specifying the default operation for your form. - * In forms that have only one handler method, that method should always be declared as the - * default. For example:
+ * activated. Essentially the default handler is specifying the default operation for your form. In + * forms that have only one handler method, that method should always be declared as the default. + * For example: * ** @HandlesEvent("Search") @@ -76,13 +76,13 @@ ** *Handler methods have two options for what to do when they have finished their processing. The - * preferred option is to return an instance of an implementation of Resolution. This keeps things - * clean and makes it a little easier to change how things work down the road. The other option - * is to return nothing, and simply use the Servlet API (available through the ActionBeanContext) to - * render a response to the user directly.
+ * preferred option is to return an instance of an implementation of Resolution. This keeps things + * clean and makes it a little easier to change how things work down the road. The other option is + * to return nothing, and simply use the Servlet API (available through the ActionBeanContext) to + * render a response to the user directly. * *An ActionBean may optionally implement the {@link ValidationErrorHandler} interface which - * allows the ActionBean to modify what happens when validation errors occur.
+ * allows the ActionBean to modify what happens when validation errors occur. * * @see Resolution * @see ActionBeanContext @@ -90,20 +90,20 @@ * @author Tim Fennell */ public interface ActionBean { - /** - * Called by the Stripes dispatcher to provide context to the ActionBean before invoking the - * handler method. Implementations should store a reference to the context for use during - * event handling. - * - * @param context ActionBeanContext associated with the current request - */ - public void setContext(ActionBeanContext context); + /** + * Called by the Stripes dispatcher to provide context to the ActionBean before invoking the + * handler method. Implementations should store a reference to the context for use during event + * handling. + * + * @param context ActionBeanContext associated with the current request + */ + public void setContext(ActionBeanContext context); - /** - * Implementations must implement this method to return a reference to the context object - * provided to the ActionBean during the call to setContext(ActionBeanContext). - * - * @return ActionBeanContext associated with the current request - */ - public ActionBeanContext getContext(); + /** + * Implementations must implement this method to return a reference to the context object provided + * to the ActionBean during the call to setContext(ActionBeanContext). + * + * @return ActionBeanContext associated with the current request + */ + public ActionBeanContext getContext(); } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/ActionBeanContext.java b/stripes/src/main/java/net/sourceforge/stripes/action/ActionBeanContext.java index f16efef60..9faaa8a78 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/ActionBeanContext.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/ActionBeanContext.java @@ -14,14 +14,12 @@ */ package net.sourceforge.stripes.action; +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.List; import java.util.Locale; - -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import net.sourceforge.stripes.controller.FlashScope; import net.sourceforge.stripes.controller.StripesConstants; import net.sourceforge.stripes.exception.SourcePageNotFoundException; @@ -29,248 +27,254 @@ import net.sourceforge.stripes.validation.ValidationErrors; /** - *Encapsulates information about the current request. Also provides access to the underlying - * Servlet API should you need to use it for any reason.
+ * Encapsulates information about the current request. Also provides access to the underlying + * Servlet API should you need to use it for any reason. * - *Developers should generally consider subclassing ActionBeanContext to provide a facade - * to contextual state for their application. Type safe getters and setter can be added to - * the subclass and used by the application, thus hiding where the information is actually - * stored. This approach is documented in more detail in the Stripes documentation on - * State Management.
+ *Developers should generally consider subclassing ActionBeanContext to provide a facade to + * contextual state for their application. Type safe getters and setter can be added to the subclass + * and used by the application, thus hiding where the information is actually stored. This approach + * is documented in more detail in the Stripes documentation on State Management. * * @author Tim Fennell */ public class ActionBeanContext { - private HttpServletRequest request; - private HttpServletResponse response; - private ServletContext servletContext; - private String eventName; - private ValidationErrors validationErrors; + private HttpServletRequest request; + private HttpServletResponse response; + private ServletContext servletContext; + private String eventName; + private ValidationErrors validationErrors; - /** - * Retrieves the HttpServletRequest object that is associated with the current request. - * @return HttpServletRequest the current request - */ - public HttpServletRequest getRequest() { - return request; - } - - /** - * Used by the DispatcherServlet to set the HttpServletRequest for the current request - * @param request the current request - */ - public void setRequest(HttpServletRequest request) { - this.request = request; - } + /** + * Retrieves the HttpServletRequest object that is associated with the current request. + * + * @return HttpServletRequest the current request + */ + public HttpServletRequest getRequest() { + return request; + } - /** - * Retrieves the HttpServletResponse that is associated with the current request. - * @return HttpServletResponse the current response - */ - public HttpServletResponse getResponse() { - return response; - } + /** + * Used by the DispatcherServlet to set the HttpServletRequest for the current request + * + * @param request the current request + */ + public void setRequest(HttpServletRequest request) { + this.request = request; + } - /** - * Used by the DispatcherServlet to set the HttpServletResponse that is associated with - * the current request. - * @param response the current response - */ - public void setResponse(HttpServletResponse response) { - this.response = response; - } + /** + * Retrieves the HttpServletResponse that is associated with the current request. + * + * @return HttpServletResponse the current response + */ + public HttpServletResponse getResponse() { + return response; + } - /** - * Retrieves the ServletContext object that is associated with the context in which the - * current request is being processed. - * @return ServletContext the current ServletContext - */ - public ServletContext getServletContext() { - return servletContext; - } + /** + * Used by the DispatcherServlet to set the HttpServletResponse that is associated with the + * current request. + * + * @param response the current response + */ + public void setResponse(HttpServletResponse response) { + this.response = response; + } - /** - * Sets the ServletContext object that is associated with the context in which the - * current request is being processed. - * @param servletContext the current ServletContext - */ - public void setServletContext(ServletContext servletContext) { - this.servletContext = servletContext; - } + /** + * Retrieves the ServletContext object that is associated with the context in which the current + * request is being processed. + * + * @return ServletContext the current ServletContext + */ + public ServletContext getServletContext() { + return servletContext; + } - /** - * Supplies the name of the event being handled. While a specific method is usually invoked on - * an ActionBean, through the use of default handlers ambiguity can arise. This allows - * ActionBeans to definitively know the name of the event that was fired. - * - * @return String the name of the event being handled - */ - public String getEventName() { - return eventName; - } + /** + * Sets the ServletContext object that is associated with the context in which the current request + * is being processed. + * + * @param servletContext the current ServletContext + */ + public void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + } - /** - * Used by the DispatcherServlet to set the name of the even being handled. - * @param eventName the name of the event being handled - */ - public void setEventName(String eventName) { - this.eventName = eventName; - } + /** + * Supplies the name of the event being handled. While a specific method is usually invoked on an + * ActionBean, through the use of default handlers ambiguity can arise. This allows ActionBeans to + * definitively know the name of the event that was fired. + * + * @return String the name of the event being handled + */ + public String getEventName() { + return eventName; + } - /** - * Returns the set of validation errors associated with the current form. Lazily - * initialized the set of errors, and will never return null. - * - * @return a Collection of validation errors - */ - public ValidationErrors getValidationErrors() { - if (this.validationErrors == null) { - this.validationErrors = new ValidationErrors(); - } + /** + * Used by the DispatcherServlet to set the name of the even being handled. + * + * @param eventName the name of the event being handled + */ + public void setEventName(String eventName) { + this.eventName = eventName; + } - return validationErrors; + /** + * Returns the set of validation errors associated with the current form. Lazily initialized the + * set of errors, and will never return null. + * + * @return a Collection of validation errors + */ + public ValidationErrors getValidationErrors() { + if (this.validationErrors == null) { + this.validationErrors = new ValidationErrors(); } - /** - * Replaces the current set of validation errors. - * @param validationErrors a collect of validation errors - */ - public void setValidationErrors(ValidationErrors validationErrors) { - this.validationErrors = validationErrors; - } + return validationErrors; + } - /** - *
Returns the default set of non-error messages associated with the current request. - * Guaranteed to always return a List, though the list may be empty. It is envisaged that - * messages will normally be added to the request as follows:
- * - *- *getContext().getMessages().add( ... ); - *- * - *To remove messages from the current request fetch the list of messages and invoke - * remove() or clear(). Messages will be made available to JSPs during the current - * request and in the subsequent request if a redirect is issued.
- * - * @return a List of Message objects associated with the current request, never null. - * @see ActionBeanContext#getMessages(String) - */ - public ListgetMessages() { - return getMessages(StripesConstants.REQ_ATTR_MESSAGES); - } + /** + * Replaces the current set of validation errors. + * + * @param validationErrors a collect of validation errors + */ + public void setValidationErrors(ValidationErrors validationErrors) { + this.validationErrors = validationErrors; + } - /** - * Returns the set of non-error messages associated with the current request under the - * specified key. Can be used to manage multiple lists of messages, for different purposes. - * Guaranteed to always return a List, though the list may be empty. It is envisaged that - * messages will normally be added to the request as follows:
- * - *- *getContext().getMessages(key).add( ... ); - *- * - *To remove messages from the current request fetch the list of messages and invoke - * remove() or clear().
- * - *Messages are stored in a {@link net.sourceforge.stripes.controller.FlashScope} for - * the current request. This means that they are available in request scope using the - * supplied key during both this request, and the subsequent request if it is the result - * of a redirect.
- * - * @return a List of Message objects associated with the current request, never null. - */ - @SuppressWarnings("unchecked") - public ListgetMessages(String key) { - FlashScope scope = FlashScope.getCurrent(getRequest(), true); - List messages = (List ) scope.get(key); + /** + * Returns the default set of non-error messages associated with the current request. Guaranteed + * to always return a List, though the list may be empty. It is envisaged that messages will + * normally be added to the request as follows: + * + * + * getContext().getMessages().add( ... ); + *+ * + *To remove messages from the current request fetch the list of messages and invoke remove() + * or clear(). Messages will be made available to JSPs during the current request and in the + * subsequent request if a redirect is issued. + * + * @return a List of Message objects associated with the current request, never null. + * @see ActionBeanContext#getMessages(String) + */ + public List
+ * + *getMessages() { + return getMessages(StripesConstants.REQ_ATTR_MESSAGES); + } - if (messages == null) { - messages = new ArrayList (); + /** + * Returns the set of non-error messages associated with the current request under the specified + * key. Can be used to manage multiple lists of messages, for different purposes. Guaranteed to + * always return a List, though the list may be empty. It is envisaged that messages will normally + * be added to the request as follows: + * + * + * getContext().getMessages(key).add( ... ); + *+ * + *To remove messages from the current request fetch the list of messages and invoke remove() + * or clear(). + * + *
Messages are stored in a {@link net.sourceforge.stripes.controller.FlashScope} for the + * current request. This means that they are available in request scope using the supplied key + * during both this request, and the subsequent request if it is the result of a redirect. + * + * @return a List of Message objects associated with the current request, never null. + */ + @SuppressWarnings("unchecked") + public List
- * + * * @author Ben Gunter * @since Stripes 1.5 */ @Retention(RetentionPolicy.RUNTIME) -@Target( { ElementType.METHOD, ElementType.TYPE }) +@Target({ElementType.METHOD, ElementType.TYPE}) @Inherited @Documented public @interface HttpCache { - /** Default value for {@link #expires()}. */ - public static final int DEFAULT_EXPIRES = Integer.MIN_VALUE; + /** Default value for {@link #expires()}. */ + public static final int DEFAULT_EXPIRES = Integer.MIN_VALUE; - /** Indicates whether the response should be cached by the client. */ - boolean allow() default true; + /** Indicates whether the response should be cached by the client. */ + boolean allow() default true; - /** - * The number of seconds into the future that the response should expire. If {@link #allow()} is - * false, then this value is ignored and zero is used. If {@link #allow()} is true and this - * value is less than zero, then no Expires header is sent. - */ - int expires() default DEFAULT_EXPIRES; + /** + * The number of seconds into the future that the response should expire. If {@link #allow()} is + * false, then this value is ignored and zero is used. If {@link #allow()} is true and this value + * is less than zero, then no Expires header is sent. + */ + int expires() default DEFAULT_EXPIRES; } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/LocalizableMessage.java b/stripes/src/main/java/net/sourceforge/stripes/action/LocalizableMessage.java index 38739c150..cc86e3876 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/LocalizableMessage.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/LocalizableMessage.java @@ -14,86 +14,87 @@ */ package net.sourceforge.stripes.action; -import net.sourceforge.stripes.controller.StripesFilter; - import java.util.Locale; import java.util.ResourceBundle; +import net.sourceforge.stripes.controller.StripesFilter; /** - * A non-error message class that can localize (or at least externalize) the message String - * in a resource bundle. The bundle used is the Stripes error message bundle, which can be - * configured but by default is called 'StripesResources.properties'. In all other ways - * this class behaves like it's parent {@link SimpleMessage}. + * A non-error message class that can localize (or at least externalize) the message String in a + * resource bundle. The bundle used is the Stripes error message bundle, which can be configured but + * by default is called 'StripesResources.properties'. In all other ways this class behaves like + * it's parent {@link SimpleMessage}. * - * @author Tim Fennell + * @author Tim Fennell */ public class LocalizableMessage extends SimpleMessage { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - private String messageKey; + private String messageKey; - /** - * Creates a new LocalizableMessage with the message key provided, and optionally zero or more - * replacement parameters to use in the message. - * - * @param messageKey a key to lookup a message in the resource bundle - * @param parameter one or more replacement parameters to insert into the message - */ - public LocalizableMessage(String messageKey, Object... parameter) { - super((String) null, parameter); - this.messageKey = messageKey; - } - - /** - * Method responsible for using the information supplied to the message object to find a - * message template. In this class this is done simply by looking up the resource - * corresponding to the messageKey supplied in the constructor. - */ - @Override - protected String getMessageTemplate(Locale locale) { - ResourceBundle bundle = StripesFilter.getConfiguration(). - getLocalizationBundleFactory().getErrorMessageBundle(locale); + /** + * Creates a new LocalizableMessage with the message key provided, and optionally zero or more + * replacement parameters to use in the message. + * + * @param messageKey a key to lookup a message in the resource bundle + * @param parameter one or more replacement parameters to insert into the message + */ + public LocalizableMessage(String messageKey, Object... parameter) { + super((String) null, parameter); + this.messageKey = messageKey; + } - return bundle.getString(messageKey); - } + /** + * Method responsible for using the information supplied to the message object to find a message + * template. In this class this is done simply by looking up the resource corresponding to the + * messageKey supplied in the constructor. + */ + @Override + protected String getMessageTemplate(Locale locale) { + ResourceBundle bundle = + StripesFilter.getConfiguration() + .getLocalizationBundleFactory() + .getErrorMessageBundle(locale); - /** - * Generated equals method which will return true if the other object is of the same - * type as this instance, and would produce the same user message. - * - * @param o an instance of LocalizableMessage or subclass thereof - * @return true if the two messages would produce the same user message, false otherwise - */ - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - if (!super.equals(o)) { - return false; - } + return bundle.getString(messageKey); + } - final LocalizableMessage that = (LocalizableMessage) o; + /** + * Generated equals method which will return true if the other object is of the same type as this + * instance, and would produce the same user message. + * + * @param o an instance of LocalizableMessage or subclass thereof + * @return true if the two messages would produce the same user message, false otherwise + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } - if (messageKey != null ? !messageKey.equals(that.messageKey) : that.messageKey != null) { - return false; - } + final LocalizableMessage that = (LocalizableMessage) o; - return true; + if (messageKey != null ? !messageKey.equals(that.messageKey) : that.messageKey != null) { + return false; } - /** Generated hashCode method. */ - @Override - public int hashCode() { - int result = super.hashCode(); - result = 29 * result + (messageKey != null ? messageKey.hashCode() : 0); - return result; - } + return true; + } - public String getMessageKey() { - return messageKey; - } + /** Generated hashCode method. */ + @Override + public int hashCode() { + int result = super.hashCode(); + result = 29 * result + (messageKey != null ? messageKey.hashCode() : 0); + return result; + } + + public String getMessageKey() { + return messageKey; + } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/Message.java b/stripes/src/main/java/net/sourceforge/stripes/action/Message.java index 49cab3c62..546ed389c 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/Message.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/Message.java @@ -14,23 +14,23 @@ */ package net.sourceforge.stripes.action; -import java.util.Locale; import java.io.Serializable; +import java.util.Locale; /** - * Represents a message that can be displayed to the user. Encapsulates commonalities - * between error messages produced as part of validation and other types of user messages - * such as warnings or feedback messages. + * Represents a message that can be displayed to the user. Encapsulates commonalities between error + * messages produced as part of validation and other types of user messages such as warnings or + * feedback messages. * * @author Tim Fennell */ public interface Message extends Serializable { - /** - * Provides a message that can be displayed to the user. The message must be a String, - * and should be in the language and locale appropriate for the user. - * - * @param locale the Locale picked for the current interaction with the user - * @return String the String message that will be displayed to the user - */ - String getMessage(Locale locale); + /** + * Provides a message that can be displayed to the user. The message must be a String, and should + * be in the language and locale appropriate for the user. + * + * @param locale the Locale picked for the current interaction with the user + * @return String the String message that will be displayed to the user + */ + String getMessage(Locale locale); } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/OnwardResolution.java b/stripes/src/main/java/net/sourceforge/stripes/action/OnwardResolution.java index a33484014..0a644eaad 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/OnwardResolution.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/OnwardResolution.java @@ -17,187 +17,181 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; - import net.sourceforge.stripes.controller.StripesFilter; import net.sourceforge.stripes.format.Formatter; import net.sourceforge.stripes.util.UrlBuilder; /** - *getMessages(String key) { + FlashScope scope = FlashScope.getCurrent(getRequest(), true); + List messages = (List ) scope.get(key); - /* - * Messages imported from previous flash scope will be present in request scope but not - * in current flash scope. Handle such cases by copying the existing messages to a new - * list in the current flash and request scopes. - */ - if (getRequest().getAttribute(key) instanceof List) { - try { - for (Message message : ((List ) getRequest().getAttribute(key))) { - messages.add(message); - } - } - catch (ClassCastException e) { - messages.clear(); - } - } + if (messages == null) { + messages = new ArrayList (); - scope.put(key, messages); + /* + * Messages imported from previous flash scope will be present in request scope but not + * in current flash scope. Handle such cases by copying the existing messages to a new + * list in the current flash and request scopes. + */ + if (getRequest().getAttribute(key) instanceof List) { + try { + for (Message message : ((List ) getRequest().getAttribute(key))) { + messages.add(message); + } + } catch (ClassCastException e) { + messages.clear(); } + } - return messages; + scope.put(key, messages); } - /** - * Gets the Locale that is being used to service the current request. This is *not* the value - * that was submitted in the request, but the value picked by the configured LocalePicker - * which takes into consideration the locales preferred in the request. - * - * @return Locale the locale being used for the current request - * @see net.sourceforge.stripes.localization.LocalePicker - */ - public Locale getLocale() { - return this.request.getLocale(); - } + return messages; + } - /** - * Returns a resolution that can be used to return the user to the page from which they - * submitted they current request. Most useful in situations where a user-correctable error - * has occurred that was too difficult or expensive to check at validation time. In that case - * an ActionBean can call setValidationErrors() and then return the resolution provided by - * this method.
- * - * @return Resolution a resolution that will forward the user to the page they came from - * @throws SourcePageNotFoundException if the information required to construct a source page - * resolution cannot be found in the request. - * @see #getSourcePage() - */ - public Resolution getSourcePageResolution() throws SourcePageNotFoundException { - String sourcePage = getSourcePage(); - if (sourcePage == null) { - throw new SourcePageNotFoundException(this); - } - else { - return new ForwardResolution(sourcePage); - } - } + /** + * Gets the Locale that is being used to service the current request. This is *not* the value that + * was submitted in the request, but the value picked by the configured LocalePicker which takes + * into consideration the locales preferred in the request. + * + * @return Locale the locale being used for the current request + * @see net.sourceforge.stripes.localization.LocalePicker + */ + public Locale getLocale() { + return this.request.getLocale(); + } - /** - *- * Returns the context-relative path to the page from which the user submitted they current - * request. - *
- * - * @return Resolution a resolution that will forward the user to the page they came from - * @throws IllegalStateException if the information required to construct a source page - * resolution cannot be found in the request. - * @see #getSourcePageResolution() - */ - public String getSourcePage() { - String sourcePage = request.getParameter(StripesConstants.URL_KEY_SOURCE_PAGE); - if (sourcePage != null) { - sourcePage = CryptoUtil.decrypt(sourcePage); - } - return sourcePage; + /** + * Returns a resolution that can be used to return the user to the page from which they submitted + * they current request. Most useful in situations where a user-correctable error has occurred + * that was too difficult or expensive to check at validation time. In that case an ActionBean can + * call setValidationErrors() and then return the resolution provided by this method. + * + * @return Resolution a resolution that will forward the user to the page they came from + * @throws SourcePageNotFoundException if the information required to construct a source page + * resolution cannot be found in the request. + * @see #getSourcePage() + */ + public Resolution getSourcePageResolution() throws SourcePageNotFoundException { + String sourcePage = getSourcePage(); + if (sourcePage == null) { + throw new SourcePageNotFoundException(this); + } else { + return new ForwardResolution(sourcePage); } + } - /** - * Returns a String with the name of the event for which the instance holds context, and - * the set of validation errors, if any. - */ - @Override - public String toString() { - return getClass().getName() + "{" + - "eventName='" + eventName + "'" + - ", validationErrors=" + validationErrors + - "}"; + /** + * Returns the context-relative path to the page from which the user submitted they current + * request. + * + * @return Resolution a resolution that will forward the user to the page they came from + * @throws IllegalStateException if the information required to construct a source page resolution + * cannot be found in the request. + * @see #getSourcePageResolution() + */ + public String getSourcePage() { + String sourcePage = request.getParameter(StripesConstants.URL_KEY_SOURCE_PAGE); + if (sourcePage != null) { + sourcePage = CryptoUtil.decrypt(sourcePage); } + return sourcePage; + } + + /** + * Returns a String with the name of the event for which the instance holds context, and the set + * of validation errors, if any. + */ + @Override + public String toString() { + return getClass().getName() + + "{" + + "eventName='" + + eventName + + "'" + + ", validationErrors=" + + validationErrors + + "}"; + } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/After.java b/stripes/src/main/java/net/sourceforge/stripes/action/After.java index bab59593f..fa319dacb 100755 --- a/stripes/src/main/java/net/sourceforge/stripes/action/After.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/After.java @@ -14,30 +14,30 @@ */ package net.sourceforge.stripes.action; -import net.sourceforge.stripes.controller.LifecycleStage; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import net.sourceforge.stripes.controller.LifecycleStage; /** - *Specifies that the annotated method should be run after the specified - * {@link LifecycleStage}(s). More than one LifecycleStage can be specified, in which case the - * method will be run after each stage completes. If no LifecycleStage is specified then the - * default is to execute the method after {@link LifecycleStage#EventHandling}. - * {@link LifecycleStage#RequestInit} cannot be specified because there is no ActionBean - * to run a method on before the ActionBean has been resolved!
+ * Specifies that the annotated method should be run after the specified {@link + * LifecycleStage}(s). More than one LifecycleStage can be specified, in which case the method will + * be run after each stage completes. If no LifecycleStage is specified then the default is to + * execute the method after {@link LifecycleStage#EventHandling}. {@link LifecycleStage#RequestInit} + * cannot be specified because there is no ActionBean to run a method on before the + * ActionBean has been resolved! + * + *The method may have any name, any access specifier (public, private etc.) and must take no + * arguments. Methods may return values; if the value is a {@link + * net.sourceforge.stripes.action.Resolution} it will be used immediately to terminate the request. + * Any other values returned will be ignored. * - *
The method may have any name, any access specifier (public, private etc.) and must take - * no arguments. Methods may return values; if the value is a - * {@link net.sourceforge.stripes.action.Resolution} it will be used immediately to terminate - * the request. Any other values returned will be ignored.
+ *Examples: * - *
Examples:
- *+ ** // Runs only after the event handling method has been run * {@literal @After} * public void doStuff() { @@ -67,14 +67,14 @@ @Inherited @Documented public @interface After { - /** One or more lifecycle stages after which the method should be called. */ - LifecycleStage[] stages() default LifecycleStage.EventHandling; + /** One or more lifecycle stages after which the method should be called. */ + LifecycleStage[] stages() default LifecycleStage.EventHandling; - /** - * Allows the method to be restricted to one or more events. By default the method will - * be executed on all events. Can be used to specify one or more events to apply the method - * to (e.g. on={"save", "update"}), or to specify one or more events not to apply - * the method to (e.g. on="!delete"). - */ - String[] on() default {}; + /** + * Allows the method to be restricted to one or more events. By default the method will be + * executed on all events. Can be used to specify one or more events to apply the method to (e.g. + * on={"save", "update"}), or to specify one or more events not to apply the method to + * (e.g. on="!delete"). + */ + String[] on() default {}; } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/Before.java b/stripes/src/main/java/net/sourceforge/stripes/action/Before.java index 4c538604f..358a57290 100755 --- a/stripes/src/main/java/net/sourceforge/stripes/action/Before.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/Before.java @@ -14,31 +14,30 @@ */ package net.sourceforge.stripes.action; -import net.sourceforge.stripes.controller.LifecycleStage; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import net.sourceforge.stripes.controller.LifecycleStage; /** - *Specifies that the annotated method should be run before the specified - * {@link LifecycleStage}(s). More than one LifecycleStage can be specified, in which case the - * method will be run before each stage. If no LifecycleStage is specified then the - * default is to execute the method before {@link LifecycleStage#EventHandling}. - * {@link LifecycleStage#RequestInit} and {@link LifecycleStage#ActionBeanResolution} - * cannot be specified because there is no ActionBean to run a method on before the - * ActionBean has been resolved!
+ * Specifies that the annotated method should be run before the specified {@link + * LifecycleStage}(s). More than one LifecycleStage can be specified, in which case the method will + * be run before each stage. If no LifecycleStage is specified then the default is to execute the + * method before {@link LifecycleStage#EventHandling}. {@link LifecycleStage#RequestInit} and {@link + * LifecycleStage#ActionBeanResolution} cannot be specified because there is no ActionBean to + * run a method on before the ActionBean has been resolved! + * + *The method may have any name, any access specifier (public, private etc.) and must take no + * arguments. Methods may return values; if the value is a {@link + * net.sourceforge.stripes.action.Resolution} it will be used immediately to terminate the request. + * Any other values returned will be ignored. * - *
The method may have any name, any access specifier (public, private etc.) and must take - * no arguments. Methods may return values; if the value is a - * {@link net.sourceforge.stripes.action.Resolution} it will be used immediately to terminate - * the request. Any other values returned will be ignored.
+ *Examples: * - *
Examples:
- *+ ** // Runs before the event handling method has been run * {@literal @Before} * public void doStuff() { @@ -68,14 +67,14 @@ @Inherited @Documented public @interface Before { - /** One or more lifecycle stages before which the method should be called. */ - LifecycleStage[] stages() default LifecycleStage.EventHandling; + /** One or more lifecycle stages before which the method should be called. */ + LifecycleStage[] stages() default LifecycleStage.EventHandling; - /** - * Allows the method to be restricted to one or more events. By default the method will - * be executed on all events. Can be used to specify one or more events to apply the method - * to (e.g. on={"save", "update"}), or to specify one or more events not to apply - * the method to (e.g. on="!delete"). - */ - String[] on() default {}; + /** + * Allows the method to be restricted to one or more events. By default the method will be + * executed on all events. Can be used to specify one or more events to apply the method to (e.g. + * on={"save", "update"}), or to specify one or more events not to apply the method to + * (e.g. on="!delete"). + */ + String[] on() default {}; } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/DefaultHandler.java b/stripes/src/main/java/net/sourceforge/stripes/action/DefaultHandler.java index d6a953bad..1bc5ab754 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/DefaultHandler.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/DefaultHandler.java @@ -14,21 +14,19 @@ */ package net.sourceforge.stripes.action; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; import java.lang.annotation.Documented; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** - * Marker annotation to specify that a method within an ActionBean is the default handler for - * events if a specific event cannot be identified. + * Marker annotation to specify that a method within an ActionBean is the default handler for events + * if a specific event cannot be identified. * * @author Tim Fennell */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Documented -public @interface DefaultHandler { - -} \ No newline at end of file +public @interface DefaultHandler {} diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/DontBind.java b/stripes/src/main/java/net/sourceforge/stripes/action/DontBind.java index 6fd14bf8e..6ec59ad6c 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/DontBind.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/DontBind.java @@ -19,19 +19,17 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - import net.sourceforge.stripes.controller.LifecycleStage; /** - * Marker annotation to specify that the event handled by the annotated method should skip - * {@link LifecycleStage#BindingAndValidation} altogether. This is useful for events which ignore - * user input, such as cancel events. Note that the presence of this annotation on an event handler + * Marker annotation to specify that the event handled by the annotated method should skip {@link + * LifecycleStage#BindingAndValidation} altogether. This is useful for events which ignore user + * input, such as cancel events. Note that the presence of this annotation on an event handler * implies {@link DontValidate} as well. - * + * * @author Ben Gunter */ @Retention(RetentionPolicy.RUNTIME) -@Target( { ElementType.METHOD }) +@Target({ElementType.METHOD}) @Documented -public @interface DontBind { -} +public @interface DontBind {} diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/DontValidate.java b/stripes/src/main/java/net/sourceforge/stripes/action/DontValidate.java index 765f2092c..ec515543e 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/DontValidate.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/DontValidate.java @@ -14,11 +14,11 @@ */ package net.sourceforge.stripes.action; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; import java.lang.annotation.Documented; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** * Specify that the event handled by the annotated method should not have validation run on it @@ -26,20 +26,20 @@ * request, there may still be errors during type conversion and binding. Such errors are also * ignored by default. That behavior can be modified using the {@link #ignoreBindingErrors()} * element of this annotation. - * + * * @author Tim Fennell */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Documented public @interface DontValidate { - /** - * If true (the default) then any validation errors that might occur during type conversion and - * binding will be ignored. If false then Stripes will forward back to the source page as it - * normally would when it encounters validation errors. In either case, any errors that occur - * during binding will be present in the {@link ActionBeanContext}. - * - * @see ActionBeanContext#getValidationErrors() - */ - boolean ignoreBindingErrors() default true; + /** + * If true (the default) then any validation errors that might occur during type conversion and + * binding will be ignored. If false then Stripes will forward back to the source page as it + * normally would when it encounters validation errors. In either case, any errors that occur + * during binding will be present in the {@link ActionBeanContext}. + * + * @see ActionBeanContext#getValidationErrors() + */ + boolean ignoreBindingErrors() default true; } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/ErrorResolution.java b/stripes/src/main/java/net/sourceforge/stripes/action/ErrorResolution.java index 6cb6d370f..cd8c11620 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/ErrorResolution.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/ErrorResolution.java @@ -14,65 +14,63 @@ */ package net.sourceforge.stripes.action; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; /** * Resolution for sending HTTP error messages back to the client. errorCode is the HTTP status code * to be sent. errorMessage is a descriptive message. - * + * * @author Aaron Porter * @since Stripes 1.5 */ public class ErrorResolution implements Resolution { - private int errorCode; - private String errorMessage; + private int errorCode; + private String errorMessage; - /** - * Sends an error response to the client using the specified status code and clears the buffer. - * - * @param errorCode the HTTP status code - */ - public ErrorResolution(int errorCode) { - this.errorCode = errorCode; - } + /** + * Sends an error response to the client using the specified status code and clears the buffer. + * + * @param errorCode the HTTP status code + */ + public ErrorResolution(int errorCode) { + this.errorCode = errorCode; + } - /** - * Sends an error response to the client using the specified status code and message and clears - * the buffer. - * - * @param errorCode the HTTP status code - * @param errorMessage a descriptive message - */ - public ErrorResolution(int errorCode, String errorMessage) { - this.errorCode = errorCode; - this.errorMessage = errorMessage; - } + /** + * Sends an error response to the client using the specified status code and message and clears + * the buffer. + * + * @param errorCode the HTTP status code + * @param errorMessage a descriptive message + */ + public ErrorResolution(int errorCode, String errorMessage) { + this.errorCode = errorCode; + this.errorMessage = errorMessage; + } - public void execute(HttpServletRequest request, HttpServletResponse response) throws Exception { - if (errorMessage != null) - response.sendError(errorCode, errorMessage); - else - response.sendError(errorCode); - } + public void execute(HttpServletRequest request, HttpServletResponse response) throws Exception { + if (errorMessage != null) response.sendError(errorCode, errorMessage); + else response.sendError(errorCode); + } - /** Accessor for the HTTP status code. */ - public int getErrorCode() { - return errorCode; - } + /** Accessor for the HTTP status code. */ + public int getErrorCode() { + return errorCode; + } - /** Setter for the HTTP status code. */ - public void setErrorCode(int errorCode) { - this.errorCode = errorCode; - } + /** Setter for the HTTP status code. */ + public void setErrorCode(int errorCode) { + this.errorCode = errorCode; + } - /** Accessor for the descriptive error message. */ - public String getErrorMessage() { - return errorMessage; - } + /** Accessor for the descriptive error message. */ + public String getErrorMessage() { + return errorMessage; + } - /** Setter for the descriptive error message. */ - public void setErrorMessage(String errorMessage) { - this.errorMessage = errorMessage; - } -} \ No newline at end of file + /** Setter for the descriptive error message. */ + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/FileBean.java b/stripes/src/main/java/net/sourceforge/stripes/action/FileBean.java index 11287364f..b5a148864 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/FileBean.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/FileBean.java @@ -17,18 +17,18 @@ import java.io.*; /** - *Represents a file that was submitted as part of an HTTP POST request. Provides methods for - * examining information about the file, and the retrieving the contents of the file. When a file - * is uploaded by a user it is stored as a temporary file on the file system, which is wrapped by an + * Represents a file that was submitted as part of an HTTP POST request. Provides methods for + * examining information about the file, and the retrieving the contents of the file. When a file is + * uploaded by a user it is stored as a temporary file on the file system, which is wrapped by an * instance of this class. This is necessary because browsers may send file upload segments before - * sending any other form parameters needed to identify what to do with the uploaded files!
+ * sending any other form parameters needed to identify what to do with the uploaded files! * *The application developer is responsible for removing this temporary file once they have - * processed it. This can be accomplished in one of two ways. Firstly a call to save(File) will - * effect a save by moving the temporary file to the desired location. In this case there + * processed it. This can be accomplished in one of two ways. Firstly a call to save(File) will + * effect a save by moving the temporary file to the desired location. In this case there * is no need to call delete(), although doing so will not delete the saved file. The second way is - * to simply call delete(). This is more applicable when consuming the file as an InputStream. An - * example code fragment for reading a text based file might look like this:
+ * to simply call delete(). This is more applicable when consuming the file as an InputStream. An + * example code fragment for reading a text based file might look like this: * ** FileBean bean = getUserIcon(); @@ -45,219 +45,214 @@ * @author Tim Fennell */ public class FileBean { - private String contentType; - private String fileName; - private File file; - private String charset; - private boolean saved; + private String contentType; + private String fileName; + private File file; + private String charset; + private boolean saved; + /** + * Constructs a FileBean pointing to an on-disk representation of the file uploaded by the user. + * + * @param file the File object on the server which holds the uploaded contents of the file + * @param contentType the content type of the file declared by the browser during upload + * @param originalName the name of the file as declared by the user's browser + */ + public FileBean(File file, String contentType, String originalName) { + this.file = file; + this.contentType = contentType; + this.fileName = originalName; + } - /** - * Constructs a FileBean pointing to an on-disk representation of the file uploaded by the user. - * - * @param file the File object on the server which holds the uploaded contents of the file - * @param contentType the content type of the file declared by the browser during upload - * @param originalName the name of the file as declared by the user's browser - */ - public FileBean(File file, String contentType, String originalName) { - this.file = file; - this.contentType = contentType; - this.fileName = originalName; - } - - /** - * Constructs a FileBean pointing to an on-disk representation of the file uploaded by the user. - * - * @param file the File object on the server which holds the uploaded contents of the file - * @param contentType the content type of the file declared by the browser during upload - * @param originalName the name of the file as declared by the user's browser - * @param charset the charset specified by the servlet request - */ - public FileBean(File file, String contentType, String originalName, String charset) { - this.file = file; - this.contentType = contentType; - this.fileName = originalName; - this.charset = charset; - } + /** + * Constructs a FileBean pointing to an on-disk representation of the file uploaded by the user. + * + * @param file the File object on the server which holds the uploaded contents of the file + * @param contentType the content type of the file declared by the browser during upload + * @param originalName the name of the file as declared by the user's browser + * @param charset the charset specified by the servlet request + */ + public FileBean(File file, String contentType, String originalName, String charset) { + this.file = file; + this.contentType = contentType; + this.fileName = originalName; + this.charset = charset; + } - /** - * Returns the name of the file that the user selected and uploaded (this is not necessarily - * the name that the underlying file is now stored on the server using). - */ - public String getFileName() { - return fileName; - } + /** + * Returns the name of the file that the user selected and uploaded (this is not necessarily the + * name that the underlying file is now stored on the server using). + */ + public String getFileName() { + return fileName; + } - /** - * Returns the content type of the file that the user selected and uploaded. - */ - public String getContentType() { - return contentType; - } + /** Returns the content type of the file that the user selected and uploaded. */ + public String getContentType() { + return contentType; + } - /** - * Gets the size of the file that was uploaded. - */ - public long getSize() { - return this.file.length(); - } + /** Gets the size of the file that was uploaded. */ + public long getSize() { + return this.file.length(); + } - /** - * Gets an input stream to read from the file uploaded - */ - public InputStream getInputStream() throws IOException { - return new FileInputStream(this.file); - } - - /** - * Gets a reader to read characters from the uploaded file. If the servlet request specifies a - * charset, then that charset is used. Otherwise, the reader uses the default charset. - * - * @return a new reader - * @throws UnsupportedEncodingException - * @throws IOException - */ - public Reader getReader() throws UnsupportedEncodingException, IOException { - if (charset == null) { - return new InputStreamReader(getInputStream()); - } - else { - return getReader(charset); - } - } + /** Gets an input stream to read from the file uploaded */ + public InputStream getInputStream() throws IOException { + return new FileInputStream(this.file); + } - /** - * Gets a reader to read characters from the uploaded file using the given charset. - * - * @param charset the charset the reader should use - * @return a new reader - * @throws UnsupportedEncodingException - * @throws IOException - */ - public Reader getReader(String charset) throws UnsupportedEncodingException, IOException { - return new InputStreamReader(getInputStream(), charset); + /** + * Gets a reader to read characters from the uploaded file. If the servlet request specifies a + * charset, then that charset is used. Otherwise, the reader uses the default charset. + * + * @return a new reader + * @throws UnsupportedEncodingException + * @throws IOException + */ + public Reader getReader() throws UnsupportedEncodingException, IOException { + if (charset == null) { + return new InputStreamReader(getInputStream()); + } else { + return getReader(charset); } + } + + /** + * Gets a reader to read characters from the uploaded file using the given charset. + * + * @param charset the charset the reader should use + * @return a new reader + * @throws UnsupportedEncodingException + * @throws IOException + */ + public Reader getReader(String charset) throws UnsupportedEncodingException, IOException { + return new InputStreamReader(getInputStream(), charset); + } - /** - * Saves the uploaded file to the location on disk represented by File. First attempts a - * simple rename of the underlying file that was created during upload as this is the - * most efficient route. If the rename fails an attempt is made to copy the file bit - * by bit to the new File and then the temporary file is removed. - * - * @param toFile a File object representing a location - * @throws IOException if the save will fail for a reason that we can detect up front, for - * example, missing files, permissions etc. or we try to save get a failure. - */ - public void save(File toFile) throws IOException { - // Since File.renameTo doesn't tell you anything about why it failed, we test - // for some common reasons for failure ahead of time and give a bit more info - if (!this.file.exists()) { - throw new IOException - ("Some time between uploading and saving we lost the file " - + this.file.getAbsolutePath() + " - where did it go?."); - } + /** + * Saves the uploaded file to the location on disk represented by File. First attempts a simple + * rename of the underlying file that was created during upload as this is the most efficient + * route. If the rename fails an attempt is made to copy the file bit by bit to the new File and + * then the temporary file is removed. + * + * @param toFile a File object representing a location + * @throws IOException if the save will fail for a reason that we can detect up front, for + * example, missing files, permissions etc. or we try to save get a failure. + */ + public void save(File toFile) throws IOException { + // Since File.renameTo doesn't tell you anything about why it failed, we test + // for some common reasons for failure ahead of time and give a bit more info + if (!this.file.exists()) { + throw new IOException( + "Some time between uploading and saving we lost the file " + + this.file.getAbsolutePath() + + " - where did it go?."); + } - if (!this.file.canWrite()) { - throw new IOException - ("Some time between uploading and saving we lost the ability to write to the file " - + this.file.getAbsolutePath() + " - writability is required to move the file."); - } + if (!this.file.canWrite()) { + throw new IOException( + "Some time between uploading and saving we lost the ability to write to the file " + + this.file.getAbsolutePath() + + " - writability is required to move the file."); + } - File parent = toFile.getAbsoluteFile().getParentFile(); - if (toFile.exists() && !toFile.canWrite()) { - throw new IOException("Cannot overwrite existing file at "+ toFile.getAbsolutePath()); - } - else if (!parent.exists() && !parent.mkdirs()) { - throw new IOException("Parent directory of specified file does not exist and cannot " + - " be created. File location supplied: " + toFile.getAbsolutePath()); - } - else if (!toFile.exists() && !parent.canWrite()) { - throw new IOException("Cannot create new file at location: " + toFile.getAbsolutePath()); - } + File parent = toFile.getAbsoluteFile().getParentFile(); + if (toFile.exists() && !toFile.canWrite()) { + throw new IOException("Cannot overwrite existing file at " + toFile.getAbsolutePath()); + } else if (!parent.exists() && !parent.mkdirs()) { + throw new IOException( + "Parent directory of specified file does not exist and cannot " + + " be created. File location supplied: " + + toFile.getAbsolutePath()); + } else if (!toFile.exists() && !parent.canWrite()) { + throw new IOException("Cannot create new file at location: " + toFile.getAbsolutePath()); + } - this.saved = this.file.renameTo(toFile); + this.saved = this.file.renameTo(toFile); - // If the rename didn't work, try copying the darn thing bit by bit - if (this.saved == false) { - saveViaCopy(toFile); - } + // If the rename didn't work, try copying the darn thing bit by bit + if (this.saved == false) { + saveViaCopy(toFile); } + } - /** - * Attempts to save the uploaded file to the specified file by performing a stream - * based copy. This is only used when a rename cannot be executed, e.g. because the - * target file is on a different file system than the temporary file. - * - * @param toFile the file to save to - */ - protected void saveViaCopy(File toFile) throws IOException { - OutputStream out = null; - InputStream in = null; - try { - out = new FileOutputStream(toFile); - in = new FileInputStream(this.file); + /** + * Attempts to save the uploaded file to the specified file by performing a stream based copy. + * This is only used when a rename cannot be executed, e.g. because the target file is on a + * different file system than the temporary file. + * + * @param toFile the file to save to + */ + protected void saveViaCopy(File toFile) throws IOException { + OutputStream out = null; + InputStream in = null; + try { + out = new FileOutputStream(toFile); + in = new FileInputStream(this.file); - byte[] buffer = new byte[1024]; - for (int count; (count = in.read(buffer)) > 0;) { - out.write(buffer, 0, count); - } + byte[] buffer = new byte[1024]; + for (int count; (count = in.read(buffer)) > 0; ) { + out.write(buffer, 0, count); + } - out.close(); - out = null; - in.close(); - in = null; + out.close(); + out = null; + in.close(); + in = null; - this.file.delete(); - this.saved = true; - } - finally { - try { - if (out != null) - out.close(); - } - catch (Exception e) {} - try { - if (in != null) - in.close(); - } - catch (Exception e) {} - } + this.file.delete(); + this.saved = true; + } finally { + try { + if (out != null) out.close(); + } catch (Exception e) { + } + try { + if (in != null) in.close(); + } catch (Exception e) { + } } + } - /** - * Deletes the temporary file associated with this file upload if one still exists. If save() - * has already been called then there is no temporary file any more, and this is a no-op. - * - * @throws IOException if the delete will fail for a reason we can detect up front, or if - * we try to delete and get a failure - */ - public void delete() throws IOException { - if (!this.saved) { - // Since File.delete doesn't tell you anything about why it failed, we test - // for some common reasons for failure ahead of time and give a bit more info - if (!this.file.exists()) { - throw new IOException - ("Some time between uploading and saving we lost the file " - + this.file.getAbsolutePath() + " - where did it go?."); - } + /** + * Deletes the temporary file associated with this file upload if one still exists. If save() has + * already been called then there is no temporary file any more, and this is a no-op. + * + * @throws IOException if the delete will fail for a reason we can detect up front, or if we try + * to delete and get a failure + */ + public void delete() throws IOException { + if (!this.saved) { + // Since File.delete doesn't tell you anything about why it failed, we test + // for some common reasons for failure ahead of time and give a bit more info + if (!this.file.exists()) { + throw new IOException( + "Some time between uploading and saving we lost the file " + + this.file.getAbsolutePath() + + " - where did it go?."); + } - if (!this.file.canWrite()) { - throw new IOException - ("Some time between uploading and saving we lost the ability to write to the file " - + this.file.getAbsolutePath() + " - writability is required to delete the file."); - } - this.file.delete(); - } + if (!this.file.canWrite()) { + throw new IOException( + "Some time between uploading and saving we lost the ability to write to the file " + + this.file.getAbsolutePath() + + " - writability is required to delete the file."); + } + this.file.delete(); } + } - /** - * Returns the name of the file and the content type in a String format. - */ - @Override - public String toString() { - return "FileBean{" + - "contentType='" + contentType + "'" + - ", fileName='" + fileName + "'" + - "}"; - } + /** Returns the name of the file and the content type in a String format. */ + @Override + public String toString() { + return "FileBean{" + + "contentType='" + + contentType + + "'" + + ", fileName='" + + fileName + + "'" + + "}"; + } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/ForwardResolution.java b/stripes/src/main/java/net/sourceforge/stripes/action/ForwardResolution.java index 4286fcbcd..dafd237de 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/ForwardResolution.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/ForwardResolution.java @@ -14,124 +14,123 @@ */ package net.sourceforge.stripes.action; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; import net.sourceforge.stripes.controller.StripesConstants; import net.sourceforge.stripes.util.Log; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - /** - *Resolution that uses the Servlet API to forward the user to another path within the - * same web application using a server side forward.
+ * Resolution that uses the Servlet API to forward the user to another path within the same + * web application using a server side forward. * - *There is one case when this resolution will issue an include instead of a forward. The - * Servlet specification is ambiguous about what should happen when a forward is issued inside - * of an include. The behaviour varies widely by container, from outputting only the content - * of the forward, to only the content prior to the include! To make this behaviour more - * consistent the ForwardResolution will automatically determine if it is executing inside of - * an include, and if that is the case it will include the appropriate URL instead of - * forwarding to it. This behaviour can be turned off be calling - * {@literal autoInclude(false)}.
+ *There is one case when this resolution will issue an include instead of a forward. The Servlet + * specification is ambiguous about what should happen when a forward is issued inside of an + * include. The behaviour varies widely by container, from outputting only the content of the + * forward, to only the content prior to the include! To make this behaviour more consistent the + * ForwardResolution will automatically determine if it is executing inside of an include, and if + * that is the case it will include the appropriate URL instead of forwarding to it. + * This behaviour can be turned off be calling {@literal autoInclude(false)}. * *
You can optionally set an HTTP status code with {@link #setStatus(int)}, in which case a call - * to {@code response.setStatus(status)} will be made when executing the resolution.
+ * to {@code response.setStatus(status)} will be made when executing the resolution. * * @see RedirectResolution * @author Tim Fennell */ public class ForwardResolution extends OnwardResolution{ - private boolean autoInclude = true; - private static final Log log = Log.getInstance(ForwardResolution.class); - private String event; - private Integer status; + private boolean autoInclude = true; + private static final Log log = Log.getInstance(ForwardResolution.class); + private String event; + private Integer status; - /** - * Simple constructor that takes in the path to forward the user to. - * @param path the path within the web application that the user should be forwarded to - */ - public ForwardResolution(String path) { - super(path); - } - - /** - * Constructs a ForwardResolution that will forward to the URL appropriate for - * the ActionBean supplied. This constructor should be preferred when forwarding - * to an ActionBean as it will ensure the correct URL is always used. - * - * @param beanType the Class object representing the ActionBean to redirect to - */ - public ForwardResolution(Class extends ActionBean> beanType) { - super(beanType); - } + /** + * Simple constructor that takes in the path to forward the user to. + * + * @param path the path within the web application that the user should be forwarded to + */ + public ForwardResolution(String path) { + super(path); + } - /** - * Constructs a ForwardResolution that will forward to the URL appropriate for - * the ActionBean supplied. This constructor should be preferred when forwarding - * to an ActionBean as it will ensure the correct URL is always used. - * - * @param beanType the Class object representing the ActionBean to redirect to - * @param event the event that should be triggered on the redirect - */ - public ForwardResolution(Class extends ActionBean> beanType, String event) { - super(beanType, event); - this.event = event; - } + /** + * Constructs a ForwardResolution that will forward to the URL appropriate for the ActionBean + * supplied. This constructor should be preferred when forwarding to an ActionBean as it will + * ensure the correct URL is always used. + * + * @param beanType the Class object representing the ActionBean to redirect to + */ + public ForwardResolution(Class extends ActionBean> beanType) { + super(beanType); + } - /** - * If true then the ForwardResolution will automatically detect when it is executing - * as part of a server-side Include and include the supplied URL instead of - * forwarding to it. Defaults to true. - * - * @param auto whether or not to automatically detect and use includes - */ - public void autoInclude(boolean auto) { - this.autoInclude = auto; - } + /** + * Constructs a ForwardResolution that will forward to the URL appropriate for the ActionBean + * supplied. This constructor should be preferred when forwarding to an ActionBean as it will + * ensure the correct URL is always used. + * + * @param beanType the Class object representing the ActionBean to redirect to + * @param event the event that should be triggered on the redirect + */ + public ForwardResolution(Class extends ActionBean> beanType, String event) { + super(beanType, event); + this.event = event; + } - /** Get the HTTP status, or null
if none was explicitly set. */ - public Integer getStatus() { - return status; - } + /** + * If true then the ForwardResolution will automatically detect when it is executing as part of a + * server-side Include and include the supplied URL instead of forwarding to it. Defaults + * to true. + * + * @param auto whether or not to automatically detect and use includes + */ + public void autoInclude(boolean auto) { + this.autoInclude = auto; + } - /** - * Explicitly sets an HTTP status code, in which case a call to {@code response.setStatus(status)} - * will be made when executing the resolution. - */ - public ForwardResolution setStatus(int status) { - this.status = status; - return this; - } + /** Get the HTTP status, ornull
if none was explicitly set. */ + public Integer getStatus() { + return status; + } - /** - * Attempts to forward the user to the specified path. - * @throws ServletException thrown when the Servlet container encounters an error - * @throws IOException thrown when the Servlet container encounters an error - */ - public void execute(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + /** + * Explicitly sets an HTTP status code, in which case a call to {@code response.setStatus(status)} + * will be made when executing the resolution. + */ + public ForwardResolution setStatus(int status) { + this.status = status; + return this; + } - if (status != null) { - response.setStatus(status); - } - String path = getUrl(request.getLocale()); + /** + * Attempts to forward the user to the specified path. + * + * @throws ServletException thrown when the Servlet container encounters an error + * @throws IOException thrown when the Servlet container encounters an error + */ + public void execute(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { - // Set event name as a request attribute - String oldEvent = (String) request.getAttribute(StripesConstants.REQ_ATTR_EVENT_NAME); - request.setAttribute(StripesConstants.REQ_ATTR_EVENT_NAME, event); + if (status != null) { + response.setStatus(status); + } + String path = getUrl(request.getLocale()); - // Figure out if we're inside an include, and use an include instead of a forward - if (autoInclude && request.getAttribute(StripesConstants.REQ_ATTR_INCLUDE_PATH) != null) { - log.trace("Including URL: ", path); - request.getRequestDispatcher(path).include(request, response); - } - else { - log.trace("Forwarding to URL: ", path); - request.getRequestDispatcher(path).forward(request, response); - } + // Set event name as a request attribute + String oldEvent = (String) request.getAttribute(StripesConstants.REQ_ATTR_EVENT_NAME); + request.setAttribute(StripesConstants.REQ_ATTR_EVENT_NAME, event); - // Revert event name to its original value - request.setAttribute(StripesConstants.REQ_ATTR_EVENT_NAME, oldEvent); + // Figure out if we're inside an include, and use an include instead of a forward + if (autoInclude && request.getAttribute(StripesConstants.REQ_ATTR_INCLUDE_PATH) != null) { + log.trace("Including URL: ", path); + request.getRequestDispatcher(path).include(request, response); + } else { + log.trace("Forwarding to URL: ", path); + request.getRequestDispatcher(path).forward(request, response); } + + // Revert event name to its original value + request.setAttribute(StripesConstants.REQ_ATTR_EVENT_NAME, oldEvent); + } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/HandlesEvent.java b/stripes/src/main/java/net/sourceforge/stripes/action/HandlesEvent.java index 528662a0b..683b05838 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/HandlesEvent.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/HandlesEvent.java @@ -14,15 +14,15 @@ */ package net.sourceforge.stripes.action; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; import java.lang.annotation.Documented; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** - * Annotation used by ActionBean to declare that a method is capable of handling a named event - * being submitted by a client. Used by the AnnotatedClassActionResolver to map requests to the + * Annotation used by ActionBean to declare that a method is capable of handling a named event being + * submitted by a client. Used by the AnnotatedClassActionResolver to map requests to the * appropriate method to handle them at run time. * * @author Tim Fennell @@ -31,6 +31,6 @@ @Target({ElementType.METHOD}) @Documented public @interface HandlesEvent { - /** The name of the event that will be handled by the annotated method. */ - String value(); + /** The name of the event that will be handled by the annotated method. */ + String value(); } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/HttpCache.java b/stripes/src/main/java/net/sourceforge/stripes/action/HttpCache.java index 921294b1e..700629125 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/HttpCache.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/HttpCache.java @@ -22,44 +22,42 @@ import java.lang.annotation.Target; /** - ** This annotation can be applied to an event handler method or to an {@link ActionBean} class to * suggest to the HTTP client how it should cache the response. Classes will inherit this annotation * from their superclass. Method-level annotations override class-level annotations. This means, for * example, that applying {@code @HttpCache(allow=false)} to an {@link ActionBean} class turns off * client-side caching for all events except those that are annotated with * {@code @HttpCache(allow=true)}. - *
- *- * Some examples: + * + *
Some examples: + * *
- *
- *- {@code @HttpCache} - Same behavior as if the annotation were not present. No headers are - * set.
- *- {@code @HttpCache(allow=true)} - Same as above.
- *- {@code @HttpCache(allow=false)} - Set headers to disable caching and immediately expire the - * document.
- *- {@code @HttpCache(expires=600)} - Caching is allowed. The document expires in 10 minutes.
+ *- {@code @HttpCache} - Same behavior as if the annotation were not present. No headers are + * set. + *
- {@code @HttpCache(allow=true)} - Same as above. + *
- {@code @HttpCache(allow=false)} - Set headers to disable caching and immediately expire the + * document. + *
- {@code @HttpCache(expires=600)} - Caching is allowed. The document expires in 10 minutes. *
Abstract class that provides a consistent API for all Resolutions that send the user onward to - * another view - either by forwarding, redirecting or some other mechanism. Provides methods - * for getting and setting the path that the user should be sent to next.
+ * Abstract class that provides a consistent API for all Resolutions that send the user onward to + * another view - either by forwarding, redirecting or some other mechanism. Provides methods for + * getting and setting the path that the user should be sent to next. * *The rather odd looking generic declaration on this class is called a self-bounding generic * type. The declaration allows methods in this class like {@link #addParameter(String, Object...)} - * to return the appropriate type when accessed through subclasses. I.e. - * {@code RedirectResolution.addParameter(String, Object...)} will return a reference of type - * RedirectResolution instead of OnwardResolution.
+ * to return the appropriate type when accessed through subclasses. I.e. {@code + * RedirectResolution.addParameter(String, Object...)} will return a reference of type + * RedirectResolution instead of OnwardResolution. * * @author Tim Fennell */ public abstract class OnwardResolution> implements Resolution { - /** Initial value for fields to indicate they were not changed when null has special meaning */ - private static final String VALUE_NOT_SET = "VALUE_NOT_SET"; - - private String path; - private String event = VALUE_NOT_SET; - private Map parameters = new HashMap (); - private String anchor; - - /** - * Default constructor that takes the supplied path and stores it for use. - * @param path the path to which the resolution should navigate - */ - public OnwardResolution(String path) { - if (path==null) { - throw new IllegalArgumentException("path cannot be null"); - } - this.path = path; - } - - /** - * Constructor that will extract the url binding for the ActionBean class supplied and - * use that as the path for the resolution. - * - * @param beanType a Class that represents an ActionBean - */ - public OnwardResolution(Class extends ActionBean> beanType) { - this(StripesFilter.getConfiguration().getActionResolver().getUrlBinding(beanType)); + /** Initial value for fields to indicate they were not changed when null has special meaning */ + private static final String VALUE_NOT_SET = "VALUE_NOT_SET"; + + private String path; + private String event = VALUE_NOT_SET; + private Map parameters = new HashMap (); + private String anchor; + + /** + * Default constructor that takes the supplied path and stores it for use. + * + * @param path the path to which the resolution should navigate + */ + public OnwardResolution(String path) { + if (path == null) { + throw new IllegalArgumentException("path cannot be null"); } - - /** - * Constructor that will extract the url binding for the ActionBean class supplied and - * use that as the path for the resolution and adds a parameter to ensure that the - * specified event is invoked. - * - * @param beanType a Class that represents an ActionBean - * @param event the String name of the event to trigger on navigation - */ - public OnwardResolution(Class extends ActionBean> beanType, String event) { - this(beanType); - this.event = event; - } - - /** Get the event name that was specified in one of the constructor calls. */ - public String getEvent() { - return event == VALUE_NOT_SET ? null : event; + this.path = path; + } + + /** + * Constructor that will extract the url binding for the ActionBean class supplied and use that as + * the path for the resolution. + * + * @param beanType a Class that represents an ActionBean + */ + public OnwardResolution(Class extends ActionBean> beanType) { + this(StripesFilter.getConfiguration().getActionResolver().getUrlBinding(beanType)); + } + + /** + * Constructor that will extract the url binding for the ActionBean class supplied and use that as + * the path for the resolution and adds a parameter to ensure that the specified event is invoked. + * + * @param beanType a Class that represents an ActionBean + * @param event the String name of the event to trigger on navigation + */ + public OnwardResolution(Class extends ActionBean> beanType, String event) { + this(beanType); + this.event = event; + } + + /** Get the event name that was specified in one of the constructor calls. */ + public String getEvent() { + return event == VALUE_NOT_SET ? null : event; + } + + /** Return true if an event name was specified when this instance was constructed. */ + public boolean isEventSpecified() { + return event != VALUE_NOT_SET; + } + + /** Accessor for the path that the user should be sent to. */ + public String getPath() { + return path; + } + + /** Setter for the path that the user should be sent to. */ + public void setPath(String path) { + this.path = path; + } + + /** Get the name of the anchor to be appended to the URL. */ + protected String getAnchor() { + return anchor; + } + + /** Set the name of the anchor to be appended to the URL. */ + @SuppressWarnings("unchecked") + protected T setAnchor(String anchor) { + this.anchor = anchor; + return (T) this; + } + + /** + * Method that will work for this class and subclasses; returns a String containing the class + * name, and the path to which it will send the user. + */ + @Override + public String toString() { + return getClass().getSimpleName() + "{" + "path='" + getPath() + "'" + "}"; + } + + /** + * Adds a request parameter with zero or more values to the URL. Values may be supplied using + * varargs, or alternatively by suppling a single value parameter which is an instance of + * Collection. + * + * Note that this method is additive. Therefore writing things like {@code + * builder.addParameter("p", "one").addParameter("p", "two");} will add both {@code p=one} and + * {@code p=two} to the URL. + * + * @param name the name of the URL parameter + * @param values zero or more scalar values, or a single Collection + * @return this Resolution so that methods can be chained + */ + @SuppressWarnings("unchecked") + public T addParameter(String name, Object... values) { + if (this.parameters.containsKey(name)) { + Object[] src = (Object[]) this.parameters.get(name); + Object[] dst = new Object[src.length + values.length]; + System.arraycopy(src, 0, dst, 0, src.length); + System.arraycopy(values, 0, dst, src.length, values.length); + this.parameters.put(name, dst); + } else { + this.parameters.put(name, values); } - /** Return true if an event name was specified when this instance was constructed. */ - public boolean isEventSpecified() { - return event != VALUE_NOT_SET; + return (T) this; + } + + /** + * Bulk adds one or more request parameters to the URL. Each entry in the Map represents a single + * named parameter, with the values being either a scalar value, an array or a Collection. + * + *
Note that this method is additive. If a parameter with name X has already been added and the + * map contains X as a key, the value(s) in the map will be added to the URL as well as the + * previously held values for X. + * + * @param parameters a Map of parameters as described above + * @return this Resolution so that methods can be chained + */ + @SuppressWarnings("unchecked") + public T addParameters(Map
- *parameters) { + for (Map.Entry entry : parameters.entrySet()) { + addParameter(entry.getKey(), entry.getValue()); } - /** Accessor for the path that the user should be sent to. */ - public String getPath() { - return path; + return (T) this; + } + + /** + * Provides access to the Map of parameters that has been accumulated so far for this resolution. + * The reference returned is to the internal parameters map! As such any changed made to the Map + * will be reflected in the Resolution, and any subsequent calls to addParameter(s) will be + * reflected in the Map. + * + * @return the Map of parameters for the resolution + */ + public Map getParameters() { + return parameters; + } + + /** + * Constructs the URL for the resolution by taking the path and appending any parameters supplied. + * + * @param locale the locale to be used by {@link Formatter}s when formatting parameters + */ + public String getUrl(Locale locale) { + UrlBuilder builder = new UrlBuilder(locale, getPath(), false); + if (event != VALUE_NOT_SET) { + builder.setEvent(event == null || event.length() < 1 ? null : event); } - - /** Setter for the path that the user should be sent to. */ - public void setPath(String path) { - this.path = path; - } - - /** Get the name of the anchor to be appended to the URL. */ - protected String getAnchor() { - return anchor; - } - - /** Set the name of the anchor to be appended to the URL. */ - @SuppressWarnings("unchecked") - protected T setAnchor(String anchor) { - this.anchor = anchor; - return (T) this; - } - - /** - * Method that will work for this class and subclasses; returns a String containing the - * class name, and the path to which it will send the user. - */ - @Override - public String toString() { - return getClass().getSimpleName() + "{" + - "path='" + getPath() + "'" + - "}"; - } - - /** - * Adds a request parameter with zero or more values to the URL. Values may - * be supplied using varargs, or alternatively by suppling a single value parameter which is - * an instance of Collection.
- * - *Note that this method is additive. Therefore writing things like - * {@code builder.addParameter("p", "one").addParameter("p", "two");} - * will add both {@code p=one} and {@code p=two} to the URL.
- * - * @param name the name of the URL parameter - * @param values zero or more scalar values, or a single Collection - * @return this Resolution so that methods can be chained - */ - @SuppressWarnings("unchecked") - public T addParameter(String name, Object... values) { - if (this.parameters.containsKey(name)) { - Object[] src = (Object[]) this.parameters.get(name); - Object[] dst = new Object[src.length + values.length]; - System.arraycopy(src, 0, dst, 0, src.length); - System.arraycopy(values, 0, dst, src.length, values.length); - this.parameters.put(name, dst); - } - else { - this.parameters.put(name, values); - } - - return (T) this; - } - - /** - *Bulk adds one or more request parameters to the URL. Each entry in the Map - * represents a single named parameter, with the values being either a scalar value, - * an array or a Collection.
- * - *Note that this method is additive. If a parameter with name X has already been - * added and the map contains X as a key, the value(s) in the map will be added to the - * URL as well as the previously held values for X.
- * - * @param parameters a Map of parameters as described above - * @return this Resolution so that methods can be chained - */ - @SuppressWarnings("unchecked") - public T addParameters(Mapparameters) { - for (Map.Entry entry : parameters.entrySet()) { - addParameter(entry.getKey(), entry.getValue()); - } - - return (T) this; - } - - /** - * Provides access to the Map of parameters that has been accumulated so far - * for this resolution. The reference returned is to the internal parameters - * map! As such any changed made to the Map will be reflected in the Resolution, - * and any subsequent calls to addParameter(s) will be reflected in the Map.
- * - * @return the Map of parameters for the resolution - */ - public MapgetParameters() { - return parameters; - } - - /** - * Constructs the URL for the resolution by taking the path and appending any parameters - * supplied. - * - * @param locale the locale to be used by {@link Formatter}s when formatting parameters - */ - public String getUrl(Locale locale) { - UrlBuilder builder = new UrlBuilder(locale, getPath(), false); - if (event != VALUE_NOT_SET) { - builder.setEvent(event == null || event.length() < 1 ? null : event); - } - if (anchor != null) { - builder.setAnchor(anchor); - } - builder.addParameters(this.parameters); - return builder.toString(); + if (anchor != null) { + builder.setAnchor(anchor); } + builder.addParameters(this.parameters); + return builder.toString(); + } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/RedirectResolution.java b/stripes/src/main/java/net/sourceforge/stripes/action/RedirectResolution.java index 249cde0be..ec563424b 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/RedirectResolution.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/RedirectResolution.java @@ -14,196 +14,192 @@ */ package net.sourceforge.stripes.action; -import net.sourceforge.stripes.controller.FlashScope; -import net.sourceforge.stripes.controller.StripesConstants; -import net.sourceforge.stripes.util.Log; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; - +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponseWrapper; import java.io.IOException; import java.util.Collection; import java.util.HashSet; +import net.sourceforge.stripes.controller.FlashScope; +import net.sourceforge.stripes.controller.StripesConstants; +import net.sourceforge.stripes.util.Log; /** - * Resolution that uses the Servlet API to redirect the user to another path by issuing - * a client side redirect. Unlike the ForwardResolution the RedirectResolution can send the user to + * Resolution that uses the Servlet API to redirect the user to another path by issuing a + * client side redirect. Unlike the ForwardResolution the RedirectResolution can send the user to * any URL anywhere on the web - though it is more commonly used to send the user to a location - * within the same application.
+ * within the same application. * - *
By default the RedirectResolution will prepend the context path of the web application to - * any URL before redirecting the request. To prevent the context path from being prepended - * use the constructor: {@code RedirectResolution(String,boolean)}.
+ ** - *
It is also possible to append parameters to the URL to which the user will be redirected. - * This can be done by manually adding parameters with the addParameter() and addParameters() - * methods, and by invoking includeRequestParameters() which will cause all of the current - * request parameters to be included into the URL.
+ *By default the RedirectResolution will prepend the context path of the web application to any + * URL before redirecting the request. To prevent the context path from being prepended use the + * constructor: {@code RedirectResolution(String,boolean)}. * - *
- * The redirect type can be switched from a 302 temporary redirect (default) to a 301 permanent + *
It is also possible to append parameters to the URL to which the user will be redirected. This + * can be done by manually adding parameters with the addParameter() and addParameters() methods, + * and by invoking includeRequestParameters() which will cause all of the current request parameters + * to be included into the URL. + * + *
The redirect type can be switched from a 302 temporary redirect (default) to a 301 permanent * redirect using the setPermanent method. - *
- * + * * @see ForwardResolution * @author Tim Fennell */ public class RedirectResolution extends OnwardResolution{ - private static final Log log = Log.getInstance(RedirectResolution.class); - private boolean prependContext = true; - private boolean includeRequestParameters; - private Collection beans; // used to flash action beans - private boolean permanent = false; - - /** - * Simple constructor that takes the URL to which to forward the user. Defaults to - * prepending the context path to the url supplied before redirecting. - * - * @param url the URL to which the user's browser should be re-directed. - */ - public RedirectResolution(String url) { - this(url, true); + private static final Log log = Log.getInstance(RedirectResolution.class); + private boolean prependContext = true; + private boolean includeRequestParameters; + private Collection beans; // used to flash action beans + private boolean permanent = false; + + /** + * Simple constructor that takes the URL to which to forward the user. Defaults to prepending the + * context path to the url supplied before redirecting. + * + * @param url the URL to which the user's browser should be re-directed. + */ + public RedirectResolution(String url) { + this(url, true); + } + + /** + * Constructor that allows explicit control over whether or not the context path is prepended to + * the URL before redirecting. + * + * @param url the URL to which the user's browser should be re-directed. + * @param prependContext true if the context should be prepended, false otherwise + */ + public RedirectResolution(String url, boolean prependContext) { + super(url); + this.prependContext = prependContext; + } + + /** + * Constructs a RedirectResolution that will redirect to the URL appropriate for the ActionBean + * supplied. This constructor should be preferred when redirecting to an ActionBean as it will + * ensure the correct URL is always used. + * + * @param beanType the Class object representing the ActionBean to redirect to + */ + public RedirectResolution(Class extends ActionBean> beanType) { + super(beanType); + } + + /** + * Constructs a RedirectResolution that will redirect to the URL appropriate for the ActionBean + * supplied. This constructor should be preferred when redirecting to an ActionBean as it will + * ensure the correct URL is always used. + * + * @param beanType the Class object representing the ActionBean to redirect to + * @param event the event that should be triggered on the redirect + */ + public RedirectResolution(Class extends ActionBean> beanType, String event) { + super(beanType, event); + } + + /** This method is overridden to make it public. */ + @Override + public String getAnchor() { + return super.getAnchor(); + } + + /** This method is overridden to make it public. */ + @Override + public RedirectResolution setAnchor(String anchor) { + return super.setAnchor(anchor); + } + + /** + * If set to true, will cause absolutely all request parameters present in the current request to + * be appended to the redirect URL that will be sent to the browser. Since some browsers and + * servers cannot handle extremely long URLs, care should be taken when using this method with + * large form posts. + * + * @param inc whether or not current request parameters should be included in the redirect + * @return RedirectResolution, this resolution so that methods can be chained + */ + public RedirectResolution includeRequestParameters(boolean inc) { + this.includeRequestParameters = inc; + return this; + } + + /** + * Causes the ActionBean supplied to be added to the Flash scope and made available during the + * next request cycle. + * + * @param bean the ActionBean to be added to flash scope + * @since Stripes 1.2 + */ + public RedirectResolution flash(ActionBean bean) { + if (this.beans == null) { + this.beans = new HashSet (); } - /** - * Constructor that allows explicit control over whether or not the context path is - * prepended to the URL before redirecting. - * - * @param url the URL to which the user's browser should be re-directed. - * @param prependContext true if the context should be prepended, false otherwise - */ - public RedirectResolution(String url, boolean prependContext) { - super(url); - this.prependContext = prependContext; - } - - /** - * Constructs a RedirectResolution that will redirect to the URL appropriate for - * the ActionBean supplied. This constructor should be preferred when redirecting - * to an ActionBean as it will ensure the correct URL is always used. - * - * @param beanType the Class object representing the ActionBean to redirect to - */ - public RedirectResolution(Class extends ActionBean> beanType) { - super(beanType); + this.beans.add(bean); + return this; + } + + /** + * Attempts to redirect the user to the specified URL. + * + * @throws ServletException thrown when the Servlet container encounters an error + * @throws IOException thrown when the Servlet container encounters an error + */ + @SuppressWarnings("unchecked") + public void execute(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + if (permanent) { + response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); + response = + new HttpServletResponseWrapper(response) { + + @Override + public void setStatus(int sc) {} + + @Override + public void sendRedirect(String location) throws IOException { + setHeader("Location", location); + } + }; } - - /** - * Constructs a RedirectResolution that will redirect to the URL appropriate for - * the ActionBean supplied. This constructor should be preferred when redirecting - * to an ActionBean as it will ensure the correct URL is always used. - * - * @param beanType the Class object representing the ActionBean to redirect to - * @param event the event that should be triggered on the redirect - */ - public RedirectResolution(Class extends ActionBean> beanType, String event) { - super(beanType, event); + if (this.includeRequestParameters) { + addParameters(request.getParameterMap()); } - /** This method is overridden to make it public. */ - @Override - public String getAnchor() { - return super.getAnchor(); + // Add any beans to the flash scope + if (this.beans != null) { + FlashScope flash = FlashScope.getCurrent(request, true); + for (ActionBean bean : this.beans) { + flash.put(bean); + } } - /** This method is overridden to make it public. */ - @Override - public RedirectResolution setAnchor(String anchor) { - return super.setAnchor(anchor); + // If a flash scope exists, add the parameter to the request + FlashScope flash = FlashScope.getCurrent(request, false); + if (flash != null) { + addParameter(StripesConstants.URL_KEY_FLASH_SCOPE_ID, flash.key()); } - /** - * If set to true, will cause absolutely all request parameters present in the current request - * to be appended to the redirect URL that will be sent to the browser. Since some browsers - * and servers cannot handle extremely long URLs, care should be taken when using this - * method with large form posts. - * - * @param inc whether or not current request parameters should be included in the redirect - * @return RedirectResolution, this resolution so that methods can be chained - */ - public RedirectResolution includeRequestParameters(boolean inc) { - this.includeRequestParameters = inc; - return this; + // Prepend the context path if requested + String url = getUrl(request.getLocale()); + if (prependContext) { + String contextPath = request.getContextPath(); + if (contextPath.length() > 1) url = contextPath + url; } - /** - * Causes the ActionBean supplied to be added to the Flash scope and made available - * during the next request cycle. - * - * @param bean the ActionBean to be added to flash scope - * @since Stripes 1.2 - */ - public RedirectResolution flash(ActionBean bean) { - if (this.beans == null) { - this.beans = new HashSet (); - } - - this.beans.add(bean); - return this; - } + url = response.encodeRedirectURL(url); + log.trace("Redirecting ", this.beans == null ? "" : "(w/flashed bean) ", "to URL: ", url); - /** - * Attempts to redirect the user to the specified URL. - * - * @throws ServletException thrown when the Servlet container encounters an error - * @throws IOException thrown when the Servlet container encounters an error - */ - @SuppressWarnings("unchecked") - public void execute(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - - if (permanent) { - response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); - response = new HttpServletResponseWrapper(response) { - - @Override - public void setStatus(int sc) { - } - - @Override - public void sendRedirect(String location) throws IOException { - setHeader("Location", location); - } - - }; - } - if (this.includeRequestParameters) { - addParameters(request.getParameterMap()); - } - - // Add any beans to the flash scope - if (this.beans != null) { - FlashScope flash = FlashScope.getCurrent(request, true); - for (ActionBean bean : this.beans) { - flash.put(bean); - } - } - - // If a flash scope exists, add the parameter to the request - FlashScope flash = FlashScope.getCurrent(request, false); - if (flash != null) { - addParameter(StripesConstants.URL_KEY_FLASH_SCOPE_ID, flash.key()); - } - - // Prepend the context path if requested - String url = getUrl(request.getLocale()); - if (prependContext) { - String contextPath = request.getContextPath(); - if (contextPath.length() > 1) - url = contextPath + url; - } - - url = response.encodeRedirectURL(url); - log.trace("Redirecting ", this.beans == null ? "" : "(w/flashed bean) ", "to URL: ", url); - - response.sendRedirect(url); - } + response.sendRedirect(url); + } - /** Sets the redirect type to permanent (301) instead of temporary (302). */ - public RedirectResolution setPermanent(boolean permanent) { - this.permanent = permanent; - return this; - } + /** Sets the redirect type to permanent (301) instead of temporary (302). */ + public RedirectResolution setPermanent(boolean permanent) { + this.permanent = permanent; + return this; + } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/Resolution.java b/stripes/src/main/java/net/sourceforge/stripes/action/Resolution.java index 1df2b1455..a036e05ba 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/Resolution.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/Resolution.java @@ -14,27 +14,26 @@ */ package net.sourceforge.stripes.action; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; /** * Type that is designed to be returned by "handler" methods in ActionBeans. The - * Resolution is responsible for executing the next step after the ActionBean has handled the - * user's request. In most cases this will likely be to forward the user on to the next page. + * Resolution is responsible for executing the next step after the ActionBean has handled the user's + * request. In most cases this will likely be to forward the user on to the next page. * * @see ForwardResolution * @author Tim Fennell */ public interface Resolution { - /** - * Called by the Stripes dispatcher to invoke the Resolution. Should use the request and - * response provided to direct the user to an appropriate view. - * - * @param request the current HttpServletRequest - * @param response the current HttpServletResponse - * @throws Exception exceptions of any type may be thrown if the Resolution cannot be - * executed as intended - */ - void execute(HttpServletRequest request, HttpServletResponse response) - throws Exception; + /** + * Called by the Stripes dispatcher to invoke the Resolution. Should use the request and response + * provided to direct the user to an appropriate view. + * + * @param request the current HttpServletRequest + * @param response the current HttpServletResponse + * @throws Exception exceptions of any type may be thrown if the Resolution cannot be executed as + * intended + */ + void execute(HttpServletRequest request, HttpServletResponse response) throws Exception; } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/SessionScope.java b/stripes/src/main/java/net/sourceforge/stripes/action/SessionScope.java index b9e4603ff..6677ce151 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/SessionScope.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/SessionScope.java @@ -14,47 +14,47 @@ */ package net.sourceforge.stripes.action; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.lang.annotation.ElementType; -import java.lang.annotation.Documented; /** - * Annotation that is used to specify that an ActionBean should be instantiated and stored across - * requests in the Session scope. By default ActionBeans are instantiated per-request, populated, - * used and then discarded at the end of the request cycle. Using this annotation causes an - * ActionBean to live for multiple request cycles. It will be instantiated and put into session - * on the first request that references the ActionBean. A reference to the bean will also be - * placed into RequestScope for each request that references the bean, thereby allowing the rest - * of Stripes to treat it like any other ActionBean.
+ * Annotation that is used to specify that an ActionBean should be instantiated and stored across + * requests in the Session scope. By default ActionBeans are instantiated per-request, populated, + * used and then discarded at the end of the request cycle. Using this annotation causes an + * ActionBean to live for multiple request cycles. It will be instantiated and put into session on + * the first request that references the ActionBean. A reference to the bean will also be placed + * into RequestScope for each request that references the bean, thereby allowing the rest of Stripes + * to treat it like any other ActionBean. * *Since session scope ActionBeans are not generally encouraged by the author, very few - * allowances will be made in Stripes to accommodate session scope beans. This means that - * additional mechanisms to handle session scope beans do not exist. However, there are - * general mechanisms built in to Stripes that will allow you to overcome most if not all issues - * that arise from Session scoping ActionBeans.
+ * allowances will be made in Stripes to accommodate session scope beans. This means that additional + * mechanisms to handle session scope beans do not exist. However, there are general mechanisms + * built in to Stripes that will allow you to overcome most if not all issues that arise from + * Session scoping ActionBeans. * *One major issue is how to clear out values from an ActionBean before the next request cycle. * It is suggested that this be done in the ActionBean.setContext() method, which is guaranteed to - * be invoked before any binding occurs. Note that this problem is two-fold. Firstly the browser - * does not submit values for checkboxes that are de-selected. Secondly Stripes does not invoke + * be invoked before any binding occurs. Note that this problem is two-fold. Firstly the browser + * does not submit values for checkboxes that are de-selected. Secondly Stripes does not invoke * setters for parameters submitted in the request with values equal to the empty-string. You may * choose to simply null out such fields in setContext() or use the available reference to the - * HttpServletRequest to find out if empty values were submitted for fields, and null out just - * those fields.
+ * HttpServletRequest to find out if empty values were submitted for fields, and null out just those + * fields. * - *A second major issue is in using the validation service. The validation service validates - * what was submitted in the request. Therefore if a property is marked are required, - * is present in the session scope bean, but is not submitted by the user, it will generate a - * required field error. This may or may not be desired behaviour. If it is not, it is suggested - * that the ActionBean implement the ValidationErrorHandler interface to find out about the - * validation errors generated, and take action accordingly.
+ *A second major issue is in using the validation service. The validation service validates + * what was submitted in the request. Therefore if a property is marked are required, is + * present in the session scope bean, but is not submitted by the user, it will generate a required + * field error. This may or may not be desired behaviour. If it is not, it is suggested that the + * ActionBean implement the ValidationErrorHandler interface to find out about the validation errors + * generated, and take action accordingly. * *
Lastly, an alternative to session scoping for wizard pattern/page-spanning forms that - * ActionBean authors may wish to consider is the use of the - * {@link net.sourceforge.stripes.tag.WizardFieldsTag} which will carry all the fields submitted - * in the request into the next request by writing hidden form fields.
+ * ActionBean authors may wish to consider is the use of the {@link + * net.sourceforge.stripes.tag.WizardFieldsTag} which will carry all the fields submitted in the + * request into the next request by writing hidden form fields. * * @see net.sourceforge.stripes.validation.ValidationErrorHandler * @author Tim Fennell @@ -62,5 +62,4 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented -public @interface SessionScope { -} +public @interface SessionScope {} diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/SimpleMessage.java b/stripes/src/main/java/net/sourceforge/stripes/action/SimpleMessage.java index 2de889f25..feff93bbb 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/SimpleMessage.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/SimpleMessage.java @@ -19,146 +19,144 @@ import java.util.Locale; /** - *A simple non-error message that uses the String supplied to it as the message (i.e. it does - * not look up the message in a resource bundle).
+ * A simple non-error message that uses the String supplied to it as the message (i.e. it does not + * look up the message in a resource bundle). * *Messages may contain one or more "replacement parameters". To use replacement - * parameters a message must contain the replacement token {#} where # is the numeric index of - * the replacement parameter.
+ * parameters a message must contain the replacement token {#} where # is the numeric index of the + * replacement parameter. * - *For example, to construct a message with two replacement parameters you might supply - * a message like:
+ *For example, to construct a message with two replacement parameters you might supply a message + * like: * *
Welcome back {0}, your last login was on {1,date,short}.* - *At runtime this might get replaced out to result in a message for the user that looks - * like "Welcome back Johnnie, your last login was on 01/01/2006".
+ *At runtime this might get replaced out to result in a message for the user that looks like + * "Welcome back Johnnie, your last login was on 01/01/2006". * - *
{@link java.text.MessageFormat} is used to merge the parameters in to the message and as - * a result the parameters can themselves receive formatting through the various java.text.* - * formatters.
+ *{@link java.text.MessageFormat} is used to merge the parameters in to the message and as a + * result the parameters can themselves receive formatting through the various java.text.* + * formatters. * * @author Tim Fennell * @see java.text.MessageFormat */ public class SimpleMessage implements Message { - private static final long serialVersionUID = 1L; - - private String message; - - /** - * The set of replacement parameters that will be used to create the message from the message - * template. Note that position 0 is reserved for the field name and position 1 is reserved - * for the field value. - */ - private Object[] replacementParameters; - - /** - * Constructs a message with the supplied message string and zero or more parameters - * to be merged into the message. When constructing a SimpleMessage a non-null message - * string must be supplied (though subclasses may return null if they do not rely upon it). - * - * @param message the String message to display to the user, optionally with placeholders - * for replacement parameters - * @param parameters - */ - public SimpleMessage(String message, Object... parameters) { - this.replacementParameters = parameters; - this.message = message; + private static final long serialVersionUID = 1L; + + private String message; + + /** + * The set of replacement parameters that will be used to create the message from the message + * template. Note that position 0 is reserved for the field name and position 1 is reserved for + * the field value. + */ + private Object[] replacementParameters; + + /** + * Constructs a message with the supplied message string and zero or more parameters to be merged + * into the message. When constructing a SimpleMessage a non-null message string must be supplied + * (though subclasses may return null if they do not rely upon it). + * + * @param message the String message to display to the user, optionally with placeholders for + * replacement parameters + * @param parameters + */ + public SimpleMessage(String message, Object... parameters) { + this.replacementParameters = parameters; + this.message = message; + } + + /** + * Helper constructor to allow subclasses to provide and manipulate replacement parameters without + * having to supply a message String. + * + * @param parameters zero or more parameters for replacement into the message + */ + protected SimpleMessage(Object... parameters) { + this.replacementParameters = parameters; + } + + /** + * Uses the String message passed in as the message template and combines it with any replacement + * parameters provided to construct a message for display to the user. Although SimpleMessage does + * not localize it's message string, any formatters invoked as a result of using replacement + * parameters will be in the correct locale. + * + * @param locale the locale of the current request + * @return String the message stored under the messageKey supplied + */ + public String getMessage(Locale locale) { + // Now get the message itself + String messageTemplate = getMessageTemplate(locale); + + // For compatibility with JSTL, only apply formatting if there are replacement parameters + if (this.replacementParameters != null && this.replacementParameters.length > 0) { + MessageFormat format = new MessageFormat(messageTemplate, locale); + return format.format(this.replacementParameters, new StringBuffer(), null).toString(); + } else { + return messageTemplate; } - - /** - * Helper constructor to allow subclasses to provide and manipulate replacement - * parameters without having to supply a message String. - * - * @param parameters zero or more parameters for replacement into the message - */ - protected SimpleMessage(Object... parameters) { - this.replacementParameters = parameters; + } + + /** + * Simply returns the message passed in at Construction time. Designed to be overridden by + * subclasses to lookup messages from resource bundles. + * + * @param locale the Locale of the message template desired + * @return the message (potentially with TextFormat replacement tokens). + */ + protected String getMessageTemplate(Locale locale) { + return this.message; + } + + /** + * Returns the exact message that was supplied in the constructor. This should not be called to + * render user output, but only when direct access to the String is needed for some reason. + * + * @return the exact message String passed in to the constructor + */ + public String getMessage() { + return this.message; + } + + /** Allows subclasses to access the replacement parameters for this message. */ + public Object[] getReplacementParameters() { + return this.replacementParameters; + } + + /** + * Checks equality by ensuring that the current instance and the 'other' instance are instances of + * the same class (though not necessarily SimpleMessage!) and that the message String and + * replacement parameters provided are the same. + * + * @param o another object that is a SimpleMessage or subclass thereof + * @return true if the two objects will generate the same user message, false otherwise + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; } - - /** - * Uses the String message passed in as the message template and combines it with any - * replacement parameters provided to construct a message for display to the user. Although - * SimpleMessage does not localize it's message string, any formatters invoked as a result - * of using replacement parameters will be in the correct locale. - * - * @param locale the locale of the current request - * @return String the message stored under the messageKey supplied - */ - public String getMessage(Locale locale) { - // Now get the message itself - String messageTemplate = getMessageTemplate(locale); - - // For compatibility with JSTL, only apply formatting if there are replacement parameters - if (this.replacementParameters != null && this.replacementParameters.length > 0) { - MessageFormat format = new MessageFormat(messageTemplate, locale); - return format.format(this.replacementParameters, new StringBuffer(), null).toString(); - } - else { - return messageTemplate; - } + if (o == null || getClass() != o.getClass()) { + return false; } - /** - * Simply returns the message passed in at Construction time. Designed to be overridden by - * subclasses to lookup messages from resource bundles. - * - * @param locale the Locale of the message template desired - * @return the message (potentially with TextFormat replacement tokens). - */ - protected String getMessageTemplate(Locale locale) { - return this.message; - } + final SimpleMessage that = (SimpleMessage) o; - /** - * Returns the exact message that was supplied in the constructor. This should not - * be called to render user output, but only when direct access to the String is - * needed for some reason. - * - * @return the exact message String passed in to the constructor - */ - public String getMessage() { - return this.message; + if (message != null ? !message.equals(that.message) : that.message != null) { + return false; } - - /** Allows subclasses to access the replacement parameters for this message. */ - public Object[] getReplacementParameters() { - return this.replacementParameters; + if (!Arrays.equals(replacementParameters, that.replacementParameters)) { + return false; } - /** - * Checks equality by ensuring that the current instance and the 'other' instance are - * instances of the same class (though not necessarily SimpleMessage!) and that the - * message String and replacement parameters provided are the same. - * - * @param o another object that is a SimpleMessage or subclass thereof - * @return true if the two objects will generate the same user message, false otherwise - */ - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - final SimpleMessage that = (SimpleMessage) o; - - if (message != null ? !message.equals(that.message) : that.message != null) { - return false; - } - if (!Arrays.equals(replacementParameters, that.replacementParameters)) { - return false; - } - - return true; - } + return true; + } - /** Generated hash code method. */ - @Override - public int hashCode() { - return (message != null ? message.hashCode() : 0); - } + /** Generated hash code method. */ + @Override + public int hashCode() { + return (message != null ? message.hashCode() : 0); + } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/StreamingResolution.java b/stripes/src/main/java/net/sourceforge/stripes/action/StreamingResolution.java index 7f97f6de0..c722b0dd2 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/StreamingResolution.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/StreamingResolution.java @@ -14,6 +14,9 @@ */ package net.sourceforge.stripes.action; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; @@ -25,476 +28,452 @@ import java.util.Date; import java.util.Iterator; import java.util.List; - -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import net.sourceforge.stripes.exception.StripesRuntimeException; import net.sourceforge.stripes.util.Log; import net.sourceforge.stripes.util.Range; /** - *
Resolution for streaming data back to the client (in place of forwarding the user to - * another page). Designed to be used for streaming non-page data such as generated images/charts - * and XML islands.
+ * Resolution for streaming data back to the client (in place of forwarding the user to another + * page). Designed to be used for streaming non-page data such as generated images/charts and XML + * islands. * - *Optionally supports the use of a file name which, if set, will cause a - * Content-Disposition header to be written to the output, resulting in a "Save As" type - * dialog box appearing in the user's browser. If you do not wish to supply a file name, but - * wish to achieve this behaviour, simple supply a file name of "".
+ *Optionally supports the use of a file name which, if set, will cause a Content-Disposition + * header to be written to the output, resulting in a "Save As" type dialog box appearing in the + * user's browser. If you do not wish to supply a file name, but wish to achieve this behaviour, + * simple supply a file name of "". * - *
StreamingResolution is designed to be subclassed where necessary to provide streaming - * output where the data being streamed is not contained in an InputStream or Reader. This - * would normally be done using an anonymous inner class as follows:
+ *StreamingResolution is designed to be subclassed where necessary to provide streaming output + * where the data being streamed is not contained in an InputStream or Reader. This would normally + * be done using an anonymous inner class as follows: * - *
- *return new StreamingResolution("text/xml") { + ** * @author Tim Fennell */ public class StreamingResolution implements Resolution { - /** Date format string for RFC 822 dates. */ - private static final String RFC_822_DATE_FORMAT = "EEE, d MMM yyyy HH:mm:ss Z"; - /** Boundary for use in multipart responses. */ - private static final String MULTIPART_BOUNDARY = "BOUNDARY_F7C98B76AEF711DF86D1B4FCDFD72085"; - private static final Log log = Log.getInstance(StreamingResolution.class); - private InputStream inputStream; - private Reader reader; - private String filename; - private String contentType; - private String characterEncoding; - private long lastModified = -1; - private long length = -1; - private boolean attachment; - private boolean rangeSupport = false; - private List+ * return new StreamingResolution("text/xml") { * public void stream(HttpServletResponse response) throws Exception { * // custom output generation code * response.getWriter().write(...); * // or * response.getOutputStream().write(...); * } - *}.setFilename("your-filename.xml"); - *+ * }.setFilename("your-filename.xml"); + *> byteRanges; - - /** - * Constructor only to be used when subclassing the StreamingResolution (usually using - * an anonymous inner class. If this constructor is used, and stream() is not overridden - * then an exception will be thrown! - * - * @param contentType the content type of the data in the stream (e.g. image/png) - */ - public StreamingResolution(String contentType) { - this.contentType = contentType; + /** Date format string for RFC 822 dates. */ + private static final String RFC_822_DATE_FORMAT = "EEE, d MMM yyyy HH:mm:ss Z"; + /** Boundary for use in multipart responses. */ + private static final String MULTIPART_BOUNDARY = "BOUNDARY_F7C98B76AEF711DF86D1B4FCDFD72085"; + + private static final Log log = Log.getInstance(StreamingResolution.class); + private InputStream inputStream; + private Reader reader; + private String filename; + private String contentType; + private String characterEncoding; + private long lastModified = -1; + private long length = -1; + private boolean attachment; + private boolean rangeSupport = false; + private List > byteRanges; + + /** + * Constructor only to be used when subclassing the StreamingResolution (usually using an + * anonymous inner class. If this constructor is used, and stream() is not overridden then an + * exception will be thrown! + * + * @param contentType the content type of the data in the stream (e.g. image/png) + */ + public StreamingResolution(String contentType) { + this.contentType = contentType; + } + + /** + * Constructor that builds a StreamingResolution that will stream binary data back to the client + * and identify the data as being of the specified content type. + * + * @param contentType the content type of the data in the stream (e.g. image/png) + * @param inputStream an InputStream from which to read the data to return to the client + */ + public StreamingResolution(String contentType, InputStream inputStream) { + this.contentType = contentType; + this.inputStream = inputStream; + } + + /** + * Constructor that builds a StreamingResolution that will stream character data back to the + * client and identify the data as being of the specified content type. + * + * @param contentType the content type of the data in the stream (e.g. text/xml) + * @param reader a Reader from which to read the character data to return to the client + */ + public StreamingResolution(String contentType, Reader reader) { + this.contentType = contentType; + this.reader = reader; + } + + /** + * Constructor that builds a StreamingResolution that will stream character data from a String + * back to the client and identify the data as being of the specified content type. + * + * @param contentType the content type of the data in the stream (e.g. text/xml) + * @param output a String to stream back to the client + */ + public StreamingResolution(String contentType, String output) { + this(contentType, new StringReader(output)); + } + + /** + * Sets the filename that will be the default name suggested when the user is prompted to save the + * file/stream being sent back. If the stream is not for saving by the user (i.e. it should be + * displayed or used by the browser) this value should not be set. + * + * @param filename the default filename the user will see + * @return StreamingResolution so that this method call can be chained to the constructor and + * returned + */ + public StreamingResolution setFilename(String filename) { + this.filename = filename; + setAttachment(filename != null); + return this; + } + + /** + * Sets the character encoding that will be set on the request when executing this resolution. If + * none is set, then the current character encoding (either the one selected by the LocalePicker + * or the container default one) will be used. + * + * @param characterEncoding the character encoding to use instead of the default + */ + public void setCharacterEncoding(String characterEncoding) { + this.characterEncoding = characterEncoding; + } + + /** + * Sets the modification-date timestamp. If this property is set, the browser may be able to apply + * it to the downloaded file. If this property is unset, the modification-date parameter will be + * omitted. + * + * @param lastModified The date-time (as a long) that the file was last modified. Optional. + * @return StreamingResolution so that this method call can be chained to the constructor and + * returned. + */ + public StreamingResolution setLastModified(long lastModified) { + this.lastModified = lastModified; + return this; + } + + /** + * Sets the file length. If this property is set, the file size will be reported in the HTTP + * header. This may help with file download progress indicators. If this property is unset, the + * size parameter will be omitted. + * + * @param length The length of the file in bytes. + * @return StreamingResolution so that this method call can be chained to the constructor and + * returned. + */ + public StreamingResolution setLength(long length) { + this.length = length; + return this; + } + + /** + * Indicates whether to use content-disposition attachment headers or not. (Defaults to true). + * + * @param attachment Whether the content should be treated as an attachment, or a direct download. + * @return StreamingResolution so that this method call can be chained to the constructor and + * returned. + */ + public StreamingResolution setAttachment(boolean attachment) { + this.attachment = attachment; + return this; + } + + /** + * Indicates whether byte range serving is supported by stream method. (Defaults to false). + * Besides setting this flag, the ActionBean also needs to set the length of the response and + * provide an {@link InputStream}-based input. Reasons for disabling byte range serving: + * + * + *
+ * + * Reasons for enabling byte range serving: + * + *- The stream method is overridden and does not support byte range serving + *
- The input to this {@link StreamingResolution} was created on-demand, and retrieving in + * byte ranges would redo this process for every byte range. + *
+ *
+ * + * @param rangeSupport Whether byte range serving is supported by stream method. + * @return StreamingResolution so that this method call can be chained to the constructor and + * returned. + */ + public StreamingResolution setRangeSupport(boolean rangeSupport) { + this.rangeSupport = rangeSupport; + return this; + } + + /** + * Streams data from the InputStream or Reader to the response's OutputStream or PrinterWriter, + * using a moderately sized buffer to ensure that the operation is reasonable efficient. Once the + * InputStream or Reader signaled the end of the stream, close() is called on it. + * + * @param request the HttpServletRequest being processed + * @param response the paired HttpServletResponse + * @throws IOException if there is a problem accessing one of the streams or reader/writer objects + * used. + */ + public final void execute(HttpServletRequest request, HttpServletResponse response) + throws Exception { + /*- + * Process byte ranges only when the following three conditions are met: + * - Length has been defined (without length it is impossible to efficiently stream) + * - rangeSupport has not been set to false + * - Output is binary and not character based + -*/ + if (rangeSupport && (length >= 0) && (inputStream != null)) + byteRanges = parseRangeHeader(request.getHeader("Range")); + + applyHeaders(response); + stream(response); + } + + /** + * Sets the response headers, based on what is known about the file or stream being handled. + * + * @param response the current HttpServletResponse + */ + protected void applyHeaders(HttpServletResponse response) { + if (byteRanges != null) { + response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); } - /** - * Constructor that builds a StreamingResolution that will stream binary data back to the - * client and identify the data as being of the specified content type. - * - * @param contentType the content type of the data in the stream (e.g. image/png) - * @param inputStream an InputStream from which to read the data to return to the client - */ - public StreamingResolution(String contentType, InputStream inputStream) { - this.contentType = contentType; - this.inputStream = inputStream; + if ((byteRanges == null) || (byteRanges.size() == 1)) { + response.setContentType(this.contentType); + } else { + response.setContentType("multipart/byteranges; boundary=" + MULTIPART_BOUNDARY); } - /** - * Constructor that builds a StreamingResolution that will stream character data back to the - * client and identify the data as being of the specified content type. - * - * @param contentType the content type of the data in the stream (e.g. text/xml) - * @param reader a Reader from which to read the character data to return to the client - */ - public StreamingResolution(String contentType, Reader reader) { - this.contentType = contentType; - this.reader = reader; + if (this.characterEncoding != null) { + response.setCharacterEncoding(characterEncoding); } - /** - * Constructor that builds a StreamingResolution that will stream character data from a String - * back to the client and identify the data as being of the specified content type. - * - * @param contentType the content type of the data in the stream (e.g. text/xml) - * @param output a String to stream back to the client - */ - public StreamingResolution(String contentType, String output) { - this(contentType, new StringReader(output)); + // Set Content-Length header + if (length >= 0) { + if (byteRanges == null) { + // Odd that ServletResponse.setContentLength is limited to int. + // requires downcast from long to int e.g. + // response.setContentLength((int)length); + // Workaround to allow large files: + response.addHeader("Content-Length", Long.toString(length)); + } else if (byteRanges.size() == 1) { + Range- Streaming static multimedia files + *
- Supporting resuming download managers + *
byteRange; + + byteRange = byteRanges.get(0); + response.setHeader( + "Content-Length", Long.toString(byteRange.getEnd() - byteRange.getStart() + 1)); + response.setHeader( + "Content-Range", + "bytes " + byteRange.getStart() + "-" + byteRange.getEnd() + "/" + length); + } } - /** - * Sets the filename that will be the default name suggested when the user is prompted - * to save the file/stream being sent back. If the stream is not for saving by the user - * (i.e. it should be displayed or used by the browser) this value should not be set. - * - * @param filename the default filename the user will see - * @return StreamingResolution so that this method call can be chained to the constructor - * and returned - */ - public StreamingResolution setFilename(String filename) { - this.filename = filename; - setAttachment(filename != null); - return this; + // Set Last-Modified header + if (lastModified >= 0) { + response.setDateHeader("Last-Modified", lastModified); } - /** - * Sets the character encoding that will be set on the request when executing this - * resolution. If none is set, then the current character encoding (either the one - * selected by the LocalePicker or the container default one) will be used. - * - * @param characterEncoding the character encoding to use instead of the default - */ - public void setCharacterEncoding(String characterEncoding) { - this.characterEncoding = characterEncoding; + // For Content-Disposition spec, see http://www.ietf.org/rfc/rfc2183.txt + if (attachment || filename != null) { + // Value of filename should be RFC 2047 encoded here (see RFC 2616) but few browsers + // support that, so just escape the quote for now + StringBuilder header = new StringBuilder(attachment ? "attachment" : "inline"); + if (filename != null) { + String escaped = this.filename.replace("\"", "\\\""); + header.append(";filename=\"").append(escaped).append("\""); + } + if (lastModified >= 0) { + SimpleDateFormat format = new SimpleDateFormat(RFC_822_DATE_FORMAT); + String value = format.format(new Date(lastModified)); + header.append(";modification-date=\"").append(value).append("\""); + } + if (length >= 0) { + header.append(";size=").append(length); + } + response.setHeader("Content-Disposition", header.toString()); } - - /** - * Sets the modification-date timestamp. If this property is set, the browser may be able to - * apply it to the downloaded file. If this property is unset, the modification-date parameter - * will be omitted. - * - * @param lastModified The date-time (as a long) that the file was last modified. Optional. - * @return StreamingResolution so that this method call can be chained to the constructor and - * returned. - */ - public StreamingResolution setLastModified(long lastModified) { - this.lastModified = lastModified; - return this; + } + + /** + * Parse the Range header according to RFC 2616 section 14.35.1. Example ranges from this section: + * + * + *
+ * + * @param value the value of the Range header + * @return List of sorted, non-overlapping ranges + */ + protected List- The first 500 bytes (byte offsets 0-499, inclusive): bytes=0-499 + *
- The second 500 bytes (byte offsets 500-999, inclusive): bytes=500-999 + *
- The final 500 bytes (byte offsets 9500-9999, inclusive): bytes=-500 - Or bytes=9500- + *
- The first and last bytes only (bytes 0 and 9999): bytes=0-0,-1 + *
- Several legal but not canonical specifications of the second 500 bytes (byte offsets + * 500-999, inclusive): bytes=500-600,601-999 bytes=500-700,601-999 + *
> parseRangeHeader(String value) { + Iterator > i; + String byteRangesSpecifier[], bytesUnit, byteRangeSet[]; + List > res; + long lastEnd = -1; + + if (value == null) return null; + res = new ArrayList >(); + // Parse prelude + byteRangesSpecifier = value.split("="); + if (byteRangesSpecifier.length != 2) return null; + bytesUnit = byteRangesSpecifier[0]; + byteRangeSet = byteRangesSpecifier[1].split(","); + if (!bytesUnit.equals("bytes")) return null; + // Parse individual byte ranges + for (String byteRangeSpec : byteRangeSet) { + String[] bytePos; + Long firstBytePos = null, lastBytePos = null; + + bytePos = byteRangeSpec.split("-", -1); + try { + if (bytePos[0].trim().length() > 0) firstBytePos = Long.valueOf(bytePos[0].trim()); + if (bytePos[1].trim().length() > 0) lastBytePos = Long.valueOf(bytePos[1].trim()); + } catch (NumberFormatException e) { + log.warn("Unable to parse Range header", e); + } + if ((firstBytePos == null) && (lastBytePos == null)) { + return null; + } else if (firstBytePos == null) { + firstBytePos = length - lastBytePos; + lastBytePos = length - 1; + } else if (lastBytePos == null) { + lastBytePos = length - 1; + } + if (firstBytePos > lastBytePos) return null; + if (firstBytePos < 0) return null; + if (lastBytePos >= length) return null; + res.add(new Range (firstBytePos, lastBytePos)); } - - /** - * Sets the file length. If this property is set, the file size will be reported in the HTTP - * header. This may help with file download progress indicators. If this property is unset, the - * size parameter will be omitted. - * - * @param length The length of the file in bytes. - * @return StreamingResolution so that this method call can be chained to the constructor and - * returned. - */ - public StreamingResolution setLength(long length) { - this.length = length; - return this; - } - - /** - * Indicates whether to use content-disposition attachment headers or not. (Defaults to true). - * - * @param attachment Whether the content should be treated as an attachment, or a direct - * download. - * @return StreamingResolution so that this method call can be chained to the constructor and - * returned. - */ - public StreamingResolution setAttachment(boolean attachment) { - this.attachment = attachment; - return this; + // Sort byte ranges + Collections.sort(res); + // Remove overlapping ranges + i = res.listIterator(); + while (i.hasNext()) { + Range range; + + range = i.next(); + if (lastEnd >= range.getStart()) { + range.setStart(lastEnd + 1); + if ((range.getStart() >= length) || (range.getStart() > range.getEnd())) i.remove(); + else lastEnd = range.getEnd(); + } else { + lastEnd = range.getEnd(); + } } - - /** - * Indicates whether byte range serving is supported by stream method. (Defaults to false). - * Besides setting this flag, the ActionBean also needs to set the length of the response and - * provide an {@link InputStream}-based input. Reasons for disabling byte range serving: - * - *
- * Reasons for enabling byte range serving: - *- The stream method is overridden and does not support byte range serving
- *- The input to this {@link StreamingResolution} was created on-demand, and retrieving in - * byte ranges would redo this process for every byte range.
- *- *
- * - * @param rangeSupport Whether byte range serving is supported by stream method. - * @return StreamingResolution so that this method call can be chained to the constructor and - * returned. - */ - public StreamingResolution setRangeSupport(boolean rangeSupport) { - this.rangeSupport = rangeSupport; - return this; - } - - /** - * Streams data from the InputStream or Reader to the response's OutputStream or PrinterWriter, - * using a moderately sized buffer to ensure that the operation is reasonable efficient. - * Once the InputStream or Reader signaled the end of the stream, close() is called on it. - * - * @param request the HttpServletRequest being processed - * @param response the paired HttpServletResponse - * @throws IOException if there is a problem accessing one of the streams or reader/writer - * objects used. - */ - final public void execute(HttpServletRequest request, HttpServletResponse response) - throws Exception { - /*- - * Process byte ranges only when the following three conditions are met: - * - Length has been defined (without length it is impossible to efficiently stream) - * - rangeSupport has not been set to false - * - Output is binary and not character based - -*/ - if (rangeSupport && (length >= 0) && (inputStream != null)) - byteRanges = parseRangeHeader(request.getHeader("Range")); - - applyHeaders(response); - stream(response); - } - - /** - * Sets the response headers, based on what is known about the file or stream being handled. - * - * @param response the current HttpServletResponse - */ - protected void applyHeaders(HttpServletResponse response) { - if (byteRanges != null) { - response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); - } - - if ((byteRanges == null) || (byteRanges.size() == 1)) { - response.setContentType(this.contentType); - } - else { - response.setContentType("multipart/byteranges; boundary=" + MULTIPART_BOUNDARY); - } - - if (this.characterEncoding != null) { - response.setCharacterEncoding(characterEncoding); - } - - // Set Content-Length header - if (length >= 0) { - if (byteRanges == null) { - // Odd that ServletResponse.setContentLength is limited to int. - // requires downcast from long to int e.g. - // response.setContentLength((int)length); - // Workaround to allow large files: - response.addHeader("Content-Length", Long.toString(length)); - } - else if (byteRanges.size() == 1) { - Range- Streaming static multimedia files
- *- Supporting resuming download managers
- *byteRange; - - byteRange = byteRanges.get(0); - response.setHeader("Content-Length", - Long.toString(byteRange.getEnd() - byteRange.getStart() + 1)); - response.setHeader("Content-Range", "bytes " + byteRange.getStart() + "-" - + byteRange.getEnd() + "/" + length); - } - } - - // Set Last-Modified header - if (lastModified >= 0) { - response.setDateHeader("Last-Modified", lastModified); + if (res.isEmpty()) return null; + else return res; + } + + /** + * Does the actual streaming of data through the response. If subclassed, this method should be + * overridden to stream back data other than data supplied by an InputStream or a Reader supplied + * to a constructor. If not implementing byte range serving, be sure not to set rangeSupport to + * true. + * + * If an InputStream or Reader was supplied to a constructor, this implementation uses a + * moderately sized buffer to stream data from it to the response to make the operation reasonably + * efficient, and closes the InputStream or the Reader. If an IOException occurs when closing it, + * that exception will be logged as a warning, and not thrown to avoid masking a possibly + * previously thrown exception. + * + * @param response the HttpServletResponse from which either the output stream or writer can be + * obtained + * @throws Exception if any problems arise when streaming data + */ + protected void stream(HttpServletResponse response) throws Exception { + int length = 0; + if (this.reader != null) { + char[] buffer = new char[512]; + try { + PrintWriter out = response.getWriter(); + + while ((length = this.reader.read(buffer)) != -1) { + out.write(buffer, 0, length); } - - // For Content-Disposition spec, see http://www.ietf.org/rfc/rfc2183.txt - if (attachment || filename != null) { - // Value of filename should be RFC 2047 encoded here (see RFC 2616) but few browsers - // support that, so just escape the quote for now - StringBuilder header = new StringBuilder(attachment ? "attachment" : "inline"); - if (filename != null) { - String escaped = this.filename.replace("\"", "\\\""); - header.append(";filename=\"").append(escaped).append("\""); - } - if (lastModified >= 0) { - SimpleDateFormat format = new SimpleDateFormat(RFC_822_DATE_FORMAT); - String value = format.format(new Date(lastModified)); - header.append(";modification-date=\"").append(value).append("\""); - } - if (length >= 0) { - header.append(";size=").append(length); - } - response.setHeader("Content-Disposition", header.toString()); + } finally { + try { + this.reader.close(); + } catch (Exception e) { + log.warn("Error closing reader", e); } - } - - /** - * Parse the Range header according to RFC 2616 section 14.35.1. Example ranges from this - * section: - *
- *
- * - * @param value the value of the Range header - * @return List of sorted, non-overlapping ranges - */ - protected List- The first 500 bytes (byte offsets 0-499, inclusive): bytes=0-499
- *- The second 500 bytes (byte offsets 500-999, inclusive): bytes=500-999
- *- The final 500 bytes (byte offsets 9500-9999, inclusive): bytes=-500 - Or bytes=9500-
- *- The first and last bytes only (bytes 0 and 9999): bytes=0-0,-1
- *- Several legal but not canonical specifications of the second 500 bytes (byte offsets - * 500-999, inclusive): bytes=500-600,601-999 bytes=500-700,601-999
- *> parseRangeHeader(String value) { - Iterator > i; - String byteRangesSpecifier[], bytesUnit, byteRangeSet[]; - List > res; - long lastEnd = -1; - - if (value == null) - return null; - res = new ArrayList >(); - // Parse prelude - byteRangesSpecifier = value.split("="); - if (byteRangesSpecifier.length != 2) - return null; - bytesUnit = byteRangesSpecifier[0]; - byteRangeSet = byteRangesSpecifier[1].split(","); - if (!bytesUnit.equals("bytes")) - return null; - // Parse individual byte ranges - for (String byteRangeSpec : byteRangeSet) { - String[] bytePos; - Long firstBytePos = null, lastBytePos = null; - - bytePos = byteRangeSpec.split("-", -1); - try { - if (bytePos[0].trim().length() > 0) - firstBytePos = Long.valueOf(bytePos[0].trim()); - if (bytePos[1].trim().length() > 0) - lastBytePos = Long.valueOf(bytePos[1].trim()); - } - catch (NumberFormatException e) { - log.warn("Unable to parse Range header", e); - } - if ((firstBytePos == null) && (lastBytePos == null)) { - return null; + } + } else if (this.inputStream != null) { + byte[] buffer = new byte[512]; + long count = 0; + + try { + ServletOutputStream out = response.getOutputStream(); + + if (byteRanges == null) { + while ((length = this.inputStream.read(buffer)) != -1) { + out.write(buffer, 0, length); + } + } else { + for (Range byteRange : byteRanges) { + // See RFC 2616 section 14.16 + if (byteRanges.size() > 1) { + out.print("--" + MULTIPART_BOUNDARY + "\r\n"); + out.print("Content-Type: " + contentType + "\r\n"); + out.print( + "Content-Range: bytes " + + byteRange.getStart() + + "-" + + byteRange.getEnd() + + "/" + + this.length + + "\r\n"); + out.print("\r\n"); } - else if (firstBytePos == null) { - firstBytePos = length - lastBytePos; - lastBytePos = length - 1; - } - else if (lastBytePos == null) { - lastBytePos = length - 1; - } - if (firstBytePos > lastBytePos) - return null; - if (firstBytePos < 0) - return null; - if (lastBytePos >= length) - return null; - res.add(new Range (firstBytePos, lastBytePos)); - } - // Sort byte ranges - Collections.sort(res); - // Remove overlapping ranges - i = res.listIterator(); - while (i.hasNext()) { - Range range; + if (count < byteRange.getStart()) { + long skip; - range = i.next(); - if (lastEnd >= range.getStart()) { - range.setStart(lastEnd + 1); - if ((range.getStart() >= length) || (range.getStart() > range.getEnd())) - i.remove(); - else - lastEnd = range.getEnd(); + skip = byteRange.getStart() - count; + this.inputStream.skip(skip); + count += skip; } - else { - lastEnd = range.getEnd(); + while ((length = + this.inputStream.read( + buffer, 0, (int) Math.min(buffer.length, byteRange.getEnd() + 1 - count))) + != -1) { + out.write(buffer, 0, length); + count += length; + if (byteRange.getEnd() + 1 == count) break; } - } - if (res.isEmpty()) - return null; - else - return res; - } - - /** - * - * Does the actual streaming of data through the response. If subclassed, this method should be - * overridden to stream back data other than data supplied by an InputStream or a Reader - * supplied to a constructor. If not implementing byte range serving, be sure not to set - * rangeSupport to true. - *
- * - *- * If an InputStream or Reader was supplied to a constructor, this implementation uses a - * moderately sized buffer to stream data from it to the response to make the operation - * reasonably efficient, and closes the InputStream or the Reader. If an IOException occurs when - * closing it, that exception will be logged as a warning, and not thrown to avoid - * masking a possibly previously thrown exception. - *
- * - * @param response the HttpServletResponse from which either the output stream or writer can be - * obtained - * @throws Exception if any problems arise when streaming data - */ - protected void stream(HttpServletResponse response) throws Exception { - int length = 0; - if (this.reader != null) { - char[] buffer = new char[512]; - try { - PrintWriter out = response.getWriter(); - - while ( (length = this.reader.read(buffer)) != -1 ) { - out.write(buffer, 0, length); - } - } - finally { - try { - this.reader.close(); - } - catch (Exception e) { - log.warn("Error closing reader", e); - } - } - } - else if (this.inputStream != null) { - byte[] buffer = new byte[512]; - long count = 0; - - try { - ServletOutputStream out = response.getOutputStream(); - - if (byteRanges == null) { - while ((length = this.inputStream.read(buffer)) != -1) { - out.write(buffer, 0, length); - } - } - else { - for (RangebyteRange : byteRanges) { - // See RFC 2616 section 14.16 - if (byteRanges.size() > 1) { - out.print("--" + MULTIPART_BOUNDARY + "\r\n"); - out.print("Content-Type: " + contentType + "\r\n"); - out.print("Content-Range: bytes " + byteRange.getStart() + "-" - + byteRange.getEnd() + "/" + this.length + "\r\n"); - out.print("\r\n"); - } - if (count < byteRange.getStart()) { - long skip; - - skip = byteRange.getStart() - count; - this.inputStream.skip(skip); - count += skip; - } - while ((length = this.inputStream.read(buffer, 0, (int) Math.min( - buffer.length, byteRange.getEnd() + 1 - count))) != -1) { - out.write(buffer, 0, length); - count += length; - if (byteRange.getEnd() + 1 == count) - break; - } - if (byteRanges.size() > 1) { - out.print("\r\n"); - } - } - if (byteRanges.size() > 1) - out.print("--" + MULTIPART_BOUNDARY + "--\r\n"); - } - } - finally { - try { - this.inputStream.close(); - } - catch (Exception e) { - log.warn("Error closing input stream", e); - } + if (byteRanges.size() > 1) { + out.print("\r\n"); } + } + if (byteRanges.size() > 1) out.print("--" + MULTIPART_BOUNDARY + "--\r\n"); } - else { - throw new StripesRuntimeException("A StreamingResolution was constructed without " + - "supplying a Reader or InputStream, but stream() was not overridden. Please " + - "either supply a source of streaming data, or override the stream() method."); + } finally { + try { + this.inputStream.close(); + } catch (Exception e) { + log.warn("Error closing input stream", e); } + } + } else { + throw new StripesRuntimeException( + "A StreamingResolution was constructed without " + + "supplying a Reader or InputStream, but stream() was not overridden. Please " + + "either supply a source of streaming data, or override the stream() method."); } - + } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/StrictBinding.java b/stripes/src/main/java/net/sourceforge/stripes/action/StrictBinding.java index 2449948db..901a62d8e 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/StrictBinding.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/StrictBinding.java @@ -19,12 +19,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - import net.sourceforge.stripes.validation.Validate; import net.sourceforge.stripes.validation.ValidateNestedProperties; /** - * * When applied to an {@link ActionBean}, this annotation turns on binding access controls. The * default policy is to deny binding to all properties. To enable binding on any given property, the * preferred method is to apply a {@link Validate} annotation to the property. (For nested @@ -32,54 +30,49 @@ * property in question, a naked {@link Validate} annotation may still be used to enable binding. * Alternatively, binding can be enabled or disabled through the use of the {@link #allow()} and * {@link #deny()} elements of this annotation. - *
- *- * Properties may be named explicitly or by using globs. A single star (*) matches any property of - * an element. Two stars (**) indicate any property of an element, including properties of that + * + *
Properties may be named explicitly or by using globs. A single star (*) matches any property + * of an element. Two stars (**) indicate any property of an element, including properties of that * property and so on. For security reasons, partial matches are not allowed so globs like * user.pass* will never match anything. Some examples: + * *
- *
- *- {@code *} - any property of the {@link ActionBean} itself
- *- {@code **} - any property of the {@link ActionBean} itself or its properties or their - * properties, and so on
- *- {@code user.username, user.email} - the username and email property of the user property of - * the {@link ActionBean}
- *- {@code user, user.*} - the user property and any property of the user + *
- {@code *} - any property of the {@link ActionBean} itself + *
- {@code **} - any property of the {@link ActionBean} itself or its properties or their + * properties, and so on + *
- {@code user.username, user.email} - the username and email property of the user property of + * the {@link ActionBean} + *
- {@code user, user.*} - the user property and any property of the user *
- * The {@link #allow()} and {@link #deny()} elements are of type String[], but each string in the - * array may be a comma-separated list of properties. Thus the - * {@code @StrictBinding(allow="user, user.*")} is equivalent to - * {@code @StrictBinding(allow={ "user", "user.*" }}. - *
- * + * + *The {@link #allow()} and {@link #deny()} elements are of type String[], but each string in the + * array may be a comma-separated list of properties. Thus the {@code @StrictBinding(allow="user, + * user.*")} is equivalent to {@code @StrictBinding(allow={ "user", "user.*" }}. + * * @author Ben Gunter */ @Retention(RetentionPolicy.RUNTIME) -@Target( { ElementType.TYPE }) +@Target({ElementType.TYPE}) @Documented public @interface StrictBinding { - /** - * The options for the {@link StrictBinding#defaultPolicy()} element. - */ - public enum Policy { - /** In the event of a conflict, binding is allowed */ - ALLOW, + /** The options for the {@link StrictBinding#defaultPolicy()} element. */ + public enum Policy { + /** In the event of a conflict, binding is allowed */ + ALLOW, - /** In the event of a conflict, binding is denied */ - DENY - } + /** In the event of a conflict, binding is denied */ + DENY + } - /** - * The policy to observe when a property name matches both the deny and allow lists, or when a - * property name does not match either list. - */ - Policy defaultPolicy() default Policy.DENY; + /** + * The policy to observe when a property name matches both the deny and allow lists, or when a + * property name does not match either list. + */ + Policy defaultPolicy() default Policy.DENY; - /** The list of properties that may be bound. */ - String[] allow() default ""; + /** The list of properties that may be bound. */ + String[] allow() default ""; - /** The list of properties that may not be bound. */ - String[] deny() default ""; + /** The list of properties that may not be bound. */ + String[] deny() default ""; } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/UrlBinding.java b/stripes/src/main/java/net/sourceforge/stripes/action/UrlBinding.java index d4c7f3e38..c557cdc73 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/UrlBinding.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/UrlBinding.java @@ -14,51 +14,46 @@ */ package net.sourceforge.stripes.action; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; import java.lang.annotation.Documented; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** - *
* Annotation used to bind ActionBean classes to a specific path within the web application. The * AnnotatedClassActionResolver will examine the URL submitted and extract the section that is * relative to the web-app root. That will be compared with the URL specified in the UrlBinding * annotation, to find the ActionBean that should process the chosen request. - *
- *- * Stripes supports "Clean URLs" through the {@link UrlBinding} annotation. Parameters may be + * + *
Stripes supports "Clean URLs" through the {@link UrlBinding} annotation. Parameters may be * embedded in the URL by placing the parameter name inside braces ({}). For example, * {@code @UrlBinding("/foo/{bar}/{baz}")} maps the action to "/foo" and indicates that the "bar" * and "baz" parameters may be embedded in the URL. In this case, the URL /foo/abc/123 would invoke * the action with bar set to "abc" and baz set to "123". The literal strings between parameters can * be any string. - *
- *- * The special parameter name $event may be used to embed the event name in a clean URL. For - * example, given {@code @UrlBinding("/foo/{$event}")} the "bar" event could be invoked with the - * URL /foo/bar. - *
- *- * Clean URL parameters can be assigned default values using the {@code =} operator. For example, + * + *
The special parameter name $event may be used to embed the event name in a clean URL. For + * example, given {@code @UrlBinding("/foo/{$event}")} the "bar" event could be invoked with the URL + * /foo/bar. + * + *
Clean URL parameters can be assigned default values using the {@code =} operator. For example, * {@code @UrlBinding("/foo/{bar=abc}/{baz=123}")}. If a parameter with a default value is missing * from a request URL, it will still be made available as a request parameter with the default * value. Default values are automatically embedded when building URLs with the Stripes JSP tags. * The default value for $event is determined from the {@link DefaultHandler} and may not be set in * the {@code @UrlBinding}. - *
- *- * Clean URLs support both prefix mapping ({@code /action/foo/{bar}}) and extension mapping ({@code /foo/{bar}.action}). - * Any number of parameters and/or literals may be omitted from the end of a request URL. - *
- * + * + *Clean URLs support both prefix mapping ({@code /action/foo/{bar}}) and extension mapping + * ({@code /foo/{bar}.action}). Any number of parameters and/or literals may be omitted from the end + * of a request URL. + * * @author Tim Fennell */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented public @interface UrlBinding { - /** The web-app relative URL that the ActionBean will respond to. */ - String value(); + /** The web-app relative URL that the ActionBean will respond to. */ + String value(); } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/ValidationErrorReportResolution.java b/stripes/src/main/java/net/sourceforge/stripes/action/ValidationErrorReportResolution.java index 37c0144f6..06cc7071e 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/ValidationErrorReportResolution.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/ValidationErrorReportResolution.java @@ -14,15 +14,13 @@ */ package net.sourceforge.stripes.action; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.PrintWriter; import java.util.List; import java.util.Locale; import java.util.MissingResourceException; import java.util.ResourceBundle; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import net.sourceforge.stripes.controller.StripesFilter; import net.sourceforge.stripes.exception.SourcePageNotFoundException; import net.sourceforge.stripes.tag.ErrorsTag; @@ -33,108 +31,110 @@ /** * A resolution that streams a simple HTML response to the client detailing the validation errors * that apply for an {@link ActionBeanContext}. - * + * * @author Ben Gunter * @since Stripes 1.5.5 */ public class ValidationErrorReportResolution implements Resolution { - private static final Log log = Log.getInstance(ValidationErrorReportResolution.class); - private ActionBeanContext context; - - /** Construct a new instance to report validation errors in the specified context. */ - public ValidationErrorReportResolution(ActionBeanContext context) { - this.context = context; + private static final Log log = Log.getInstance(ValidationErrorReportResolution.class); + private ActionBeanContext context; + + /** Construct a new instance to report validation errors in the specified context. */ + public ValidationErrorReportResolution(ActionBeanContext context) { + this.context = context; + } + + /** Get the action bean context on which the validation errors occurred. */ + public ActionBeanContext getContext() { + return context; + } + + public void execute(HttpServletRequest request, HttpServletResponse response) throws Exception { + // log an exception for the stack trace + SourcePageNotFoundException exception = new SourcePageNotFoundException(getContext()); + log.error(exception); + + // start the HTML error report + response.setContentType("text/html"); + PrintWriter writer = response.getWriter(); + writer.println("
"); + writer.println(""); + } + + /** + * Called by {@link #execute(HttpServletRequest, HttpServletResponse)} to write the actual + * validation errors to the client. The {@code header}, {@code footer}, {@code beforeError} and + * {@code afterError} resources are used by this method. + * + * @param request The servlet request. + * @param response The servlet response. + */ + protected void sendErrors(HttpServletRequest request, HttpServletResponse response) + throws Exception { + // Output all errors in a standard format + Locale locale = request.getLocale(); + ResourceBundle bundle = null; + + try { + bundle = + StripesFilter.getConfiguration() + .getLocalizationBundleFactory() + .getErrorMessageBundle(locale); + } catch (MissingResourceException mre) { + log.warn( + getClass().getName(), + " could not find the error messages resource bundle. ", + "As a result default headers/footers etc. will be used. Check that ", + "you have a StripesResources.properties in your classpath (unless ", + "of course you have configured a different bundle)."); } - /** Get the action bean context on which the validation errors occurred. */ - public ActionBeanContext getContext() { - return context; + // Fetch the header and footer + String header = getResource(bundle, "header", ErrorsTag.DEFAULT_HEADER); + String footer = getResource(bundle, "footer", ErrorsTag.DEFAULT_FOOTER); + String openElement = getResource(bundle, "beforeError", "Stripes validation error report
"); + writer.println(HtmlUtil.encode(exception.getMessage())); + writer.println("
Validation errors
"); + sendErrors(request, response); + writer.println("
"); + String closeElement = getResource(bundle, "afterError", " "); + + // Write out the error messages + PrintWriter writer = response.getWriter(); + writer.write(header); + + for (Listlist : getContext().getValidationErrors().values()) { + for (ValidationError fieldError : list) { + writer.write(openElement); + writer.write(HtmlUtil.encode(fieldError.getMessage(locale))); + writer.write(closeElement); + } } - public void execute(HttpServletRequest request, HttpServletResponse response) throws Exception { - // log an exception for the stack trace - SourcePageNotFoundException exception = new SourcePageNotFoundException(getContext()); - log.error(exception); - - // start the HTML error report - response.setContentType("text/html"); - PrintWriter writer = response.getWriter(); - writer.println(" "); - writer.println(""); + writer.write(footer); + } + + /** + * Utility method that is used to lookup the resources used for the error header, footer, and the + * strings that go before and after each error. + * + * @param bundle the bundle to look up the resource from + * @param name the name of the resource to lookup (prefixes will be added) + * @param fallback a value to return if no resource can be found + * @return the value to use for the named resource + */ + protected String getResource(ResourceBundle bundle, String name, String fallback) { + if (bundle == null) { + return fallback; } - /** - * Called by {@link #execute(HttpServletRequest, HttpServletResponse)} to write the actual - * validation errors to the client. The {@code header}, {@code footer}, {@code beforeError} and - * {@code afterError} resources are used by this method. - * - * @param request The servlet request. - * @param response The servlet response. - */ - protected void sendErrors(HttpServletRequest request, HttpServletResponse response) - throws Exception { - // Output all errors in a standard format - Locale locale = request.getLocale(); - ResourceBundle bundle = null; - - try { - bundle = StripesFilter.getConfiguration().getLocalizationBundleFactory() - .getErrorMessageBundle(locale); - } - catch (MissingResourceException mre) { - log.warn(getClass().getName(), " could not find the error messages resource bundle. ", - "As a result default headers/footers etc. will be used. Check that ", - "you have a StripesResources.properties in your classpath (unless ", - "of course you have configured a different bundle)."); - } - - // Fetch the header and footer - String header = getResource(bundle, "header", ErrorsTag.DEFAULT_HEADER); - String footer = getResource(bundle, "footer", ErrorsTag.DEFAULT_FOOTER); - String openElement = getResource(bundle, "beforeError", "Stripes validation error report
"); - writer.println(HtmlUtil.encode(exception.getMessage())); - writer.println("
Validation errors
"); - sendErrors(request, response); - writer.println("
"); - String closeElement = getResource(bundle, "afterError", " "); - - // Write out the error messages - PrintWriter writer = response.getWriter(); - writer.write(header); - - for (Listlist : getContext().getValidationErrors().values()) { - for (ValidationError fieldError : list) { - writer.write(openElement); - writer.write(HtmlUtil.encode(fieldError.getMessage(locale))); - writer.write(closeElement); - } - } - - writer.write(footer); + String resource; + try { + resource = bundle.getString("stripes.errors." + name); + } catch (MissingResourceException mre) { + resource = fallback; } - /** - * Utility method that is used to lookup the resources used for the error header, footer, and - * the strings that go before and after each error. - * - * @param bundle the bundle to look up the resource from - * @param name the name of the resource to lookup (prefixes will be added) - * @param fallback a value to return if no resource can be found - * @return the value to use for the named resource - */ - protected String getResource(ResourceBundle bundle, String name, String fallback) { - if (bundle == null) { - return fallback; - } - - String resource; - try { - resource = bundle.getString("stripes.errors." + name); - } - catch (MissingResourceException mre) { - resource = fallback; - } - - return resource; - } + return resource; + } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/Wizard.java b/stripes/src/main/java/net/sourceforge/stripes/action/Wizard.java index b4e17ead9..d08eea4a4 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/Wizard.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/Wizard.java @@ -14,20 +14,20 @@ */ package net.sourceforge.stripes.action; -import java.lang.annotation.Target; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Documented; +import java.lang.annotation.Target; /** - * Annotation that marks an ActionBean as representing a wizard user interface (i.e. one logical - * form or operation spread across several pages/request cycles). ActionBeans that are marked - * as Wizards are treated differently in the following ways:
+ * Annotation that marks an ActionBean as representing a wizard user interface (i.e. one logical + * form or operation spread across several pages/request cycles). ActionBeans that are marked as + * Wizards are treated differently in the following ways: * *- *
* * @author Tim Fennell @@ -37,13 +37,12 @@ @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Wizard { - /** - * An optional list of events which mark the start of the wizard flow. An event is a - * start event if it is executed before the first page in the wizard flow is - * rendered - not if it is the result of a form that targets the wizard action. - * The list is used by Stripes to disable security validation of the 'fields present' - * field in the request, as it is not necessary for start events in a wizard flow, and - * can cause problems. - */ - String[] startEvents() default {}; + /** + * An optional list of events which mark the start of the wizard flow. An event is a start event + * if it is executed before the first page in the wizard flow is rendered - not if + * it is the result of a form that targets the wizard action. The list is used by Stripes to + * disable security validation of the 'fields present' field in the request, as it is not + * necessary for start events in a wizard flow, and can cause problems. + */ + String[] startEvents() default {}; } diff --git a/stripes/src/main/java/net/sourceforge/stripes/ajax/JavaScriptBuilder.java b/stripes/src/main/java/net/sourceforge/stripes/ajax/JavaScriptBuilder.java index 0b2ef112f..e3b6a43ef 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/ajax/JavaScriptBuilder.java +++ b/stripes/src/main/java/net/sourceforge/stripes/ajax/JavaScriptBuilder.java @@ -14,13 +14,11 @@ */ package net.sourceforge.stripes.ajax; -import net.sourceforge.stripes.exception.StripesRuntimeException; -import net.sourceforge.stripes.util.Log; -import net.sourceforge.stripes.util.ReflectUtil; - import java.beans.PropertyDescriptor; import java.io.StringWriter; import java.io.Writer; +import java.lang.reflect.Array; +import java.lang.reflect.Method; import java.util.Collection; import java.util.Date; import java.util.HashMap; @@ -28,499 +26,495 @@ import java.util.Map; import java.util.Random; import java.util.Set; -import java.lang.reflect.Method; -import java.lang.reflect.Array; +import net.sourceforge.stripes.exception.StripesRuntimeException; +import net.sourceforge.stripes.util.Log; +import net.sourceforge.stripes.util.ReflectUtil; /** - *- Data from previous request cycles is maintained automatically through hidden fields
- *- Required field validation is performed only on those fields present on the page
+ *- Data from previous request cycles is maintained automatically through hidden fields + *
- Required field validation is performed only on those fields present on the page *
Builds a set of JavaScript statements that will re-construct the value of a Java object, - * including all Number, String, Enum, Boolean, Collection, Map and Array properties. Safely handles - * object graph circularities - each object will be translated only once, and all references will - * be valid.
+ * Builds a set of JavaScript statements that will re-construct the value of a Java object, + * including all Number, String, Enum, Boolean, Collection, Map and Array properties. Safely handles + * object graph circularities - each object will be translated only once, and all references will be + * valid. * - *The JavaScript created by the builder can be evaluated in JavaScript using:
+ *The JavaScript created by the builder can be evaluated in JavaScript using: * - *
- *var myObject = eval(generatedFragment); - *+ *+ * var myObject = eval(generatedFragment); + ** * @author Tim Fennell * @since Stripes 1.1 */ public class JavaScriptBuilder { - /** Log instance used to log messages. */ - private static final Log log = Log.getInstance(JavaScriptBuilder.class); - - /** Holds the set of classes representing the primitive types in Java. */ - static Set> simpleTypes = new HashSet >(); - - /** Holds the set of types that will be skipped over by default. */ - static Set > ignoredTypes = new HashSet >(); - - static { - simpleTypes.add(Byte.TYPE); - simpleTypes.add(Short.TYPE); - simpleTypes.add(Integer.TYPE); - simpleTypes.add(Long.TYPE); - simpleTypes.add(Float.TYPE); - simpleTypes.add(Double.TYPE); - simpleTypes.add(Boolean.TYPE); - simpleTypes.add(Character.TYPE); - - ignoredTypes.add(Class.class); + /** Log instance used to log messages. */ + private static final Log log = Log.getInstance(JavaScriptBuilder.class); + + /** Holds the set of classes representing the primitive types in Java. */ + static Set > simpleTypes = new HashSet >(); + + /** Holds the set of types that will be skipped over by default. */ + static Set > ignoredTypes = new HashSet >(); + + static { + simpleTypes.add(Byte.TYPE); + simpleTypes.add(Short.TYPE); + simpleTypes.add(Integer.TYPE); + simpleTypes.add(Long.TYPE); + simpleTypes.add(Float.TYPE); + simpleTypes.add(Double.TYPE); + simpleTypes.add(Boolean.TYPE); + simpleTypes.add(Character.TYPE); + + ignoredTypes.add(Class.class); + } + + /** Holds the set of objects that have been visited during conversion. */ + private Set visitedIdentities = new HashSet (); + + /** Holds a map of name to JSON value for JS Objects and Arrays. */ + private Map objectValues = new HashMap (); + + /** Holds a map of object.property = object. */ + private Map assignments = new HashMap (); + + /** Holds the root object which is to be converted to JavaScript. */ + private Object rootObject; + + /** Holds the (potentially empty) set of user classes that should be skipped over. */ + private Set > excludeClasses; + + /** Holds the (potentially empty) set of properties that should be skipped over. */ + private Set excludeProperties; + + /** Holds an optional user-supplied name for the root property. */ + private String rootVariableName = "_sj_root_" + new Random().nextInt(Integer.MAX_VALUE); + + /** + * Constructs a new JavaScriptBuilder to build JS for the root object supplied. + * + * @param root The root object from which to being translation into JavaScript + * @param objectsToExclude Zero or more Strings and/or Classes to be excluded from translation. + */ + public JavaScriptBuilder(Object root, Object... objectsToExclude) { + this.rootObject = root; + this.excludeClasses = new HashSet >(); + this.excludeProperties = new HashSet (); + + for (Object object : objectsToExclude) { + if (object instanceof Class>) addClassExclusion((Class>) object); + else if (object instanceof String) addPropertyExclusion((String) object); + else + log.warn( + "Don't know to determine exclusion for objects of type ", + object.getClass().getName(), + ". You may only pass in instances of Class and/or String."); } - /** Holds the set of objects that have been visited during conversion. */ - private Set visitedIdentities = new HashSet (); - - /** Holds a map of name to JSON value for JS Objects and Arrays. */ - private Map objectValues = new HashMap (); - - /** Holds a map of object.property = object. */ - private Map assignments = new HashMap (); - - /** Holds the root object which is to be converted to JavaScript. */ - private Object rootObject; - - /** Holds the (potentially empty) set of user classes that should be skipped over. */ - private Set > excludeClasses; - - /** Holds the (potentially empty) set of properties that should be skipped over. */ - private Set excludeProperties; - - /** Holds an optional user-supplied name for the root property. */ - private String rootVariableName = "_sj_root_" + new Random().nextInt(Integer.MAX_VALUE); - - /** - * Constructs a new JavaScriptBuilder to build JS for the root object supplied. - * - * @param root The root object from which to being translation into JavaScript - * @param objectsToExclude Zero or more Strings and/or Classes to be excluded - * from translation. - */ - public JavaScriptBuilder(Object root, Object... objectsToExclude) { - this.rootObject = root; - this.excludeClasses = new HashSet >(); - this.excludeProperties = new HashSet (); - - for (Object object : objectsToExclude) { - if (object instanceof Class>) - addClassExclusion((Class>) object); - else if (object instanceof String) - addPropertyExclusion((String) object); - else - log.warn("Don't know to determine exclusion for objects of type ", object.getClass().getName(), ". You may only pass in instances of Class and/or String."); - } - - this.excludeClasses.addAll(ignoredTypes); + this.excludeClasses.addAll(ignoredTypes); + } + + /** + * Adds one or more properties to the list of property to exclude when translating to JavaScript. + * + * @param property one or more property names to be excluded + * @return the JavaScripBuilder instance to simplify method chaining + */ + public JavaScriptBuilder addPropertyExclusion(String... property) { + for (String prop : property) { + this.excludeProperties.add(prop); } - - /** - * Adds one or more properties to the list of property to exclude when translating - * to JavaScript. - * - * @param property one or more property names to be excluded - * @return the JavaScripBuilder instance to simplify method chaining - */ - public JavaScriptBuilder addPropertyExclusion(String... property) { - for (String prop : property) { - this.excludeProperties.add(prop); - } - return this; + return this; + } + + /** + * Adds one or more properties to the list of properties to exclude when translating to + * JavaScript. + * + * @param clazz one or more classes to exclude + * @return the JavaScripBuilder instance to simplify method chaining + */ + public JavaScriptBuilder addClassExclusion(Class>... clazz) { + for (Class> c : clazz) { + this.excludeClasses.add(c); } - - /** - * Adds one or more properties to the list of properties to exclude when translating - * to JavaScript. - * - * @param clazz one or more classes to exclude - * @return the JavaScripBuilder instance to simplify method chaining - */ - public JavaScriptBuilder addClassExclusion(Class>... clazz) { - for (Class> c : clazz) { - this.excludeClasses.add(c); - } - return this; - } - - /** - * Sets an optional user-supplied root variable name. If set this name will be used - * by the building when declarind the root variable to which the JS is assigned. If - * not provided then a randomly generated name will be used. - * - * @param rootVariableName the name to use when declaring the root variable - */ - public void setRootVariableName(final String rootVariableName) { - this.rootVariableName = rootVariableName; + return this; + } + + /** + * Sets an optional user-supplied root variable name. If set this name will be used by the + * building when declarind the root variable to which the JS is assigned. If not provided then a + * randomly generated name will be used. + * + * @param rootVariableName the name to use when declaring the root variable + */ + public void setRootVariableName(final String rootVariableName) { + this.rootVariableName = rootVariableName; + } + + /** + * Returns the name used to declare the root variable to which the built JavaScript object is + * assigned. + */ + public String getRootVariableName() { + return rootVariableName; + } + + /** + * Causes the JavaScriptBuilder to navigate the properties of the supplied object and convert them + * to JavaScript. + * + * @return String a fragment of JavaScript that will define and return the JavaScript equivalent + * of the Java object supplied to the builder. + */ + public String build() { + Writer writer = new StringWriter(); + build(writer); + return writer.toString(); + } + + /** + * Causes the JavaScriptBuilder to navigate the properties of the supplied object and convert them + * to JavaScript, writing them to the supplied writer as it goes. + */ + public void build(Writer writer) { + try { + // If for some reason a caller provided us with a simple scalar object, then + // convert it and short-circuit return + if (isScalarType(this.rootObject)) { + writer.write(getScalarAsString(this.rootObject)); + writer.write(";\n"); + return; + } + + buildNode(this.rootVariableName, this.rootObject, ""); + + writer.write("var "); + writer.write(rootVariableName); + writer.write(";\n"); + + for (Map.Entry entry : objectValues.entrySet()) { + writer.append("var "); + writer.append(entry.getKey()); + writer.append(" = "); + writer.append(entry.getValue()); + writer.append(";\n"); + } + + for (Map.Entry entry : assignments.entrySet()) { + writer.append(entry.getKey()); + writer.append(" = "); + writer.append(entry.getValue()); + writer.append(";\n"); + } + + writer.append(rootVariableName).append(";\n"); + } catch (Exception e) { + throw new StripesRuntimeException( + "Could not build JavaScript for object. An " + + "exception was thrown while trying to convert a property from Java to " + + "JavaScript. The object being converted is: " + + this.rootObject, + e); } - - /** - * Returns the name used to declare the root variable to which the built - * JavaScript object is assigned. - */ - public String getRootVariableName() { - return rootVariableName; + } + + /** + * Returns true if the supplied type should be excluded from conversion, otherwise returns false. + * A class should be excluded if it is assignable to one of the types listed for exclusion, or, it + * is an array of such a type. + */ + public boolean isExcludedType(Class> type) { + for (Class> excludedType : this.excludeClasses) { + if (excludedType.isAssignableFrom(type)) { + return true; + } else if (type.isArray() && excludedType.isAssignableFrom(type.getComponentType())) { + return true; + } } - /** - * Causes the JavaScriptBuilder to navigate the properties of the supplied object and - * convert them to JavaScript. - * - * @return String a fragment of JavaScript that will define and return the JavaScript - * equivalent of the Java object supplied to the builder. - */ - public String build() { - Writer writer = new StringWriter(); - build(writer); - return writer.toString(); + return false; + } + + /** + * Returns true if the object is of a type that can be converted to a simple JavaScript scalar, + * and false otherwise. + */ + public boolean isScalarType(Object in) { + if (in == null) return true; // Though not strictly scalar, null can be treated as such + + Class> type = in.getClass(); + return simpleTypes.contains(type) + || Number.class.isAssignableFrom(type) + || String.class.isAssignableFrom(type) + || Boolean.class.isAssignableFrom(type) + || Character.class.isAssignableFrom(type) + || Date.class.isAssignableFrom(type); + } + + /** + * Fetches the value of a scalar type as a String. The input to this method may not be null, and + * must be a of a type that will return true when supplied to isScalarType(). + */ + public String getScalarAsString(Object in) { + if (in == null) return "null"; + + Class extends Object> type = in.getClass(); + + if (String.class.isAssignableFrom(type)) { + return quote((String) in); + } else if (Character.class.isAssignableFrom(type)) { + return quote(((Character) in).toString()); + } else if (Date.class.isAssignableFrom(type)) { + return "new Date(" + ((Date) in).getTime() + ")"; + } else { + return in.toString(); } - - /** - * Causes the JavaScriptBuilder to navigate the properties of the supplied object and - * convert them to JavaScript, writing them to the supplied writer as it goes. - */ - public void build(Writer writer) { - try { - // If for some reason a caller provided us with a simple scalar object, then - // convert it and short-circuit return - if (isScalarType(this.rootObject)) { - writer.write(getScalarAsString(this.rootObject)); - writer.write(";\n"); - return; - } - - buildNode(this.rootVariableName, this.rootObject, ""); - - writer.write("var "); - writer.write(rootVariableName); - writer.write(";\n"); - - for (Map.Entry entry : objectValues.entrySet()) { - writer.append("var "); - writer.append(entry.getKey()); - writer.append(" = "); - writer.append(entry.getValue()); - writer.append(";\n"); - } - - for (Map.Entry entry : assignments.entrySet()) { - writer.append(entry.getKey()); - writer.append(" = "); - writer.append(entry.getValue()); - writer.append(";\n"); - } - - writer.append(rootVariableName).append(";\n"); - } - catch (Exception e) { - throw new StripesRuntimeException("Could not build JavaScript for object. An " + - "exception was thrown while trying to convert a property from Java to " + - "JavaScript. The object being converted is: " + this.rootObject, e); - } - + } + + /** + * Quotes the supplied String and escapes all characters that could be problematic when eval()'ing + * the String in JavaScript. + * + * @param string a String to be escaped and quoted + * @return the escaped and quoted String + * @since Stripes 1.2 (thanks to Sergey Pariev) + */ + public static String quote(String string) { + if (string == null || string.length() == 0) { + return "\"\""; } - /** - * Returns true if the supplied type should be excluded from conversion, otherwise - * returns false. A class should be excluded if it is assignable to one of the types - * listed for exclusion, or, it is an array of such a type. - */ - public boolean isExcludedType(Class> type) { - for (Class> excludedType : this.excludeClasses) { - if (excludedType.isAssignableFrom(type)) { - return true; + char c = 0; + int len = string.length(); + StringBuilder sb = new StringBuilder(len + 10); + + sb.append('"'); + for (int i = 0; i < len; ++i) { + c = string.charAt(i); + switch (c) { + case '\\': + case '"': + sb.append('\\').append(c); + break; + case '\b': + sb.append("\\b"); + break; + case '\t': + sb.append("\\t"); + break; + case '\n': + sb.append("\\n"); + break; + case '\f': + sb.append("\\f"); + break; + case '\r': + sb.append("\\r"); + break; + default: + if (c < ' ') { + // The following takes lower order chars and creates unicode style + // char literals for them (e.g. \u00F3) + sb.append("\\u"); + String hex = Integer.toHexString(c); + int pad = 4 - hex.length(); + for (int j = 0; j < pad; ++j) { + sb.append("0"); } - else if (type.isArray() && excludedType.isAssignableFrom(type.getComponentType())) { - return true; - } - } - - return false; - } - - /** - * Returns true if the object is of a type that can be converted to a simple JavaScript - * scalar, and false otherwise. - */ - public boolean isScalarType(Object in) { - if (in == null) return true; // Though not strictly scalar, null can be treated as such - - Class> type = in.getClass(); - return simpleTypes.contains(type) - || Number.class.isAssignableFrom(type) - || String.class.isAssignableFrom(type) - || Boolean.class.isAssignableFrom(type) - || Character.class.isAssignableFrom(type) - || Date.class.isAssignableFrom(type); - } - - /** - * Fetches the value of a scalar type as a String. The input to this method may not be null, - * and must be a of a type that will return true when supplied to isScalarType(). - */ - public String getScalarAsString(Object in) { - if (in == null) return "null"; - - Class extends Object> type = in.getClass(); - - if (String.class.isAssignableFrom(type)) { - return quote((String) in); - } - else if (Character.class.isAssignableFrom(type)) { - return quote(((Character) in).toString()); - } - else if(Date.class.isAssignableFrom(type)) { - return "new Date(" + ((Date) in).getTime() + ")"; - } - else { - return in.toString(); - } + sb.append(hex); + } else { + sb.append(c); + } + } } - /** - * Quotes the supplied String and escapes all characters that could be problematic - * when eval()'ing the String in JavaScript. - * - * @param string a String to be escaped and quoted - * @return the escaped and quoted String - * @since Stripes 1.2 (thanks to Sergey Pariev) - */ - public static String quote(String string) { - if (string == null || string.length() == 0) { - return "\"\""; - } - - char c = 0; - int len = string.length(); - StringBuilder sb = new StringBuilder(len + 10); - - sb.append('"'); - for (int i = 0; i < len; ++i) { - c = string.charAt(i); - switch (c) { - case '\\': - case '"': - sb.append('\\').append(c); - break; - case '\b': - sb.append("\\b"); - break; - case '\t': - sb.append("\\t"); - break; - case '\n': - sb.append("\\n"); - break; - case '\f': - sb.append("\\f"); - break; - case '\r': - sb.append("\\r"); - break; - default: - if (c < ' ') { - // The following takes lower order chars and creates unicode style - // char literals for them (e.g. \u00F3) - sb.append("\\u"); - String hex = Integer.toHexString(c); - int pad = 4 - hex.length(); - for (int j=0; j ) in, propertyPrefix); + } else if (in.getClass().isArray()) { + buildArrayNode(targetName, in, propertyPrefix); + } else if (Map.class.isAssignableFrom(in.getClass())) { + buildMapNode(targetName, (Map, ?>) in, propertyPrefix); + } else { + buildObjectNode(targetName, in, propertyPrefix); + } + + this.assignments.put(name, targetName); } - - /** - * Determines the type of the object being translated and dispatches to the - * build*Node() method. Generates the temporary name of the object being translated, - * checks to ensure that the object has not already been translated, and ensure that - * the object is correctly inserted into the set of assignments. - * - * @param name The name that should appear on the left hand side of the assignment - * statement once a value for the object has been generated. - * @param in The object being translated. - */ - void buildNode(String name, Object in, String propertyPrefix) throws Exception { - int systemId = System.identityHashCode(in); - String targetName = "_sj_" + systemId; - - if (this.visitedIdentities.contains(systemId)) { - this.assignments.put(name, targetName); - } - else if (isExcludedType(in.getClass())) { - // Do nothing, it's being excluded!! - } - else { - this.visitedIdentities.add(systemId); - - if (Collection.class.isAssignableFrom(in.getClass())) { - buildCollectionNode(targetName, (Collection>) in, propertyPrefix); - } - else if (in.getClass().isArray()) { - buildArrayNode(targetName, in, propertyPrefix); - } - else if (Map.class.isAssignableFrom(in.getClass())) { - buildMapNode(targetName, (Map, ?>) in, propertyPrefix); - } - else { - buildObjectNode(targetName, in, propertyPrefix); + } + + /** + * Processes a Java Object that conforms to JavaBean conventions. Scalar properties of the object + * are converted to a JSON format object declaration which is inserted into the "objectValues" + * instance level map. Nested non-scalar objects are processed separately and then setup for + * re-attachment using the instance level "assignments" map. + * + * In most cases just the JavaBean properties will be translated. In the case of Java 5 enums, + * two additional properties will be translated, one each for the enum's 'ordinal' and 'name' + * properties. + * + * @param targetName The generated name assigned to the Object being translated + * @param in The Object who's JavaBean properties are to be translated + */ + void buildObjectNode(String targetName, Object in, String propertyPrefix) throws Exception { + StringBuilder out = new StringBuilder(); + out.append("{"); + PropertyDescriptor[] props = ReflectUtil.getPropertyDescriptors(in.getClass()); + + for (PropertyDescriptor property : props) { + try { + Method readMethod = property.getReadMethod(); + String fullPropertyName = + (propertyPrefix != null && propertyPrefix.length() > 0 ? propertyPrefix + '.' : "") + + property.getName(); + if ((readMethod != null) && !this.excludeProperties.contains(fullPropertyName)) { + Object value = property.getReadMethod().invoke(in); + + if (isExcludedType(property.getPropertyType())) { + continue; + } + + if (isScalarType(value)) { + if (out.length() > 1) { + out.append(", "); } - - this.assignments.put(name, targetName); + out.append(property.getName()); + out.append(":"); + out.append(getScalarAsString(value)); + } else { + buildNode(targetName + "." + property.getName(), value, fullPropertyName); + } } + } catch (Exception e) { + log.warn( + e, + "Could not translate property [", + property.getName(), + "] of type [", + property.getPropertyType().getName(), + "] due to an exception."); + } } - /** - *
Processes a Java Object that conforms to JavaBean conventions. Scalar properties of the - * object are converted to a JSON format object declaration which is inserted into the - * "objectValues" instance level map. Nested non-scalar objects are processed separately and - * then setup for re-attachment using the instance level "assignments" map.
- * - *In most cases just the JavaBean properties will be translated. In the case of Java 5 - * enums, two additional properties will be translated, one each for the enum's 'ordinal' - * and 'name' properties.
- * - * @param targetName The generated name assigned to the Object being translated - * @param in The Object who's JavaBean properties are to be translated - */ - void buildObjectNode(String targetName, Object in, String propertyPrefix) throws Exception { - StringBuilder out = new StringBuilder(); - out.append("{"); - PropertyDescriptor[] props = ReflectUtil.getPropertyDescriptors(in.getClass()); - - for (PropertyDescriptor property : props) { - try { - Method readMethod = property.getReadMethod(); - String fullPropertyName = (propertyPrefix != null && propertyPrefix.length() > 0 ? propertyPrefix + '.' : "") + - property.getName(); - if ((readMethod != null) && !this.excludeProperties.contains(fullPropertyName)) { - Object value = property.getReadMethod().invoke(in); - - if (isExcludedType(property.getPropertyType())) { - continue; - } - - if (isScalarType(value)) { - if (out.length() > 1) { - out.append(", "); - } - out.append(property.getName()); - out.append(":"); - out.append( getScalarAsString(value) ); - } - else { - buildNode(targetName + "." + property.getName(), value, fullPropertyName); - } - } - } - catch (Exception e) { - log.warn(e, "Could not translate property [", property.getName(), "] of type [", - property.getPropertyType().getName(), "] due to an exception."); - } - } - - // Do something a little extra for enums - if (Enum.class.isAssignableFrom(in.getClass())) { - Enum> e = (Enum>) in; + // Do something a little extra for enums + if (Enum.class.isAssignableFrom(in.getClass())) { + Enum> e = (Enum>) in; - if (out.length() > 1) { out.append(", "); } - out.append("ordinal:").append( getScalarAsString(e.ordinal()) ); - out.append(", name:").append( getScalarAsString(e.name()) ); - } - - out.append("}"); - this.objectValues.put(targetName, out.toString()); + if (out.length() > 1) { + out.append(", "); + } + out.append("ordinal:").append(getScalarAsString(e.ordinal())); + out.append(", name:").append(getScalarAsString(e.name())); } - /** - * Builds a JavaScript object node from a java Map. The keys of the map are used to - * define the properties of the JavaScript object. As such it is assumed that the keys - * are either primitives, Strings or toString() cleanly. The values of the map are used - * to generate the values of the object properties. Scalar values are inserted directly - * into the JSON representation, while complex types are converted separately and then - * attached using assignments. - * - * @param targetName The generated name assigned to the Map being translated - * @param in The Map being translated - */ - void buildMapNode(String targetName, Map,?> in, String propertyPrefix) throws Exception { - StringBuilder out = new StringBuilder(); - out.append("{"); - - for (Map.Entry,?> entry : in.entrySet()) { - String propertyName = getScalarAsString(entry.getKey()); - Object value = entry.getValue(); - - if (this.excludeProperties.contains(propertyPrefix + '[' + propertyName + ']')) { - // Do nothing, it's being excluded!! - } - else if (isScalarType(value)) { - if (out.length() > 1) { - out.append(", "); - } - out.append(propertyName); - out.append(":"); - out.append( getScalarAsString(value) ); - } - else { - buildNode(targetName + "[" + propertyName + "]", value, propertyPrefix + "[" + propertyName + "]"); - } + out.append("}"); + this.objectValues.put(targetName, out.toString()); + } + + /** + * Builds a JavaScript object node from a java Map. The keys of the map are used to define the + * properties of the JavaScript object. As such it is assumed that the keys are either primitives, + * Strings or toString() cleanly. The values of the map are used to generate the values of the + * object properties. Scalar values are inserted directly into the JSON representation, while + * complex types are converted separately and then attached using assignments. + * + * @param targetName The generated name assigned to the Map being translated + * @param in The Map being translated + */ + void buildMapNode(String targetName, Map, ?> in, String propertyPrefix) throws Exception { + StringBuilder out = new StringBuilder(); + out.append("{"); + + for (Map.Entry, ?> entry : in.entrySet()) { + String propertyName = getScalarAsString(entry.getKey()); + Object value = entry.getValue(); + + if (this.excludeProperties.contains(propertyPrefix + '[' + propertyName + ']')) { + // Do nothing, it's being excluded!! + } else if (isScalarType(value)) { + if (out.length() > 1) { + out.append(", "); } - - out.append("}"); - this.objectValues.put(targetName, out.toString()); + out.append(propertyName); + out.append(":"); + out.append(getScalarAsString(value)); + } else { + buildNode( + targetName + "[" + propertyName + "]", + value, + propertyPrefix + "[" + propertyName + "]"); + } } - /** - * Builds a JavaScript array node from a Java array. Scalar values are inserted directly - * into the array definition. Complex values are processed separately - they are inserted - * into the JSON array as null to maintain ordering, and re-attached later using assignments. - * - * @param targetName The generated name of the array node being translated. - * @param in The Array being translated. - */ - void buildArrayNode(String targetName, Object in, String propertyPrefix) throws Exception { - StringBuilder out = new StringBuilder(); - out.append("["); - - int length = Array.getLength(in); - for (int i=0; iin, String propertyPrefix) throws Exception { - buildArrayNode(targetName, in.toArray(), propertyPrefix); - } + out.append("]"); + this.objectValues.put(targetName, out.toString()); + } + + /** + * Builds an object node that is of type collection. Simply converts the collection to an array, + * and delegates to buildArrayNode(). + */ + void buildCollectionNode(String targetName, Collection> in, String propertyPrefix) + throws Exception { + buildArrayNode(targetName, in.toArray(), propertyPrefix); + } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/ajax/JavaScriptResolution.java b/stripes/src/main/java/net/sourceforge/stripes/ajax/JavaScriptResolution.java index aeded5223..ade9f409b 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/ajax/JavaScriptResolution.java +++ b/stripes/src/main/java/net/sourceforge/stripes/ajax/JavaScriptResolution.java @@ -14,65 +14,60 @@ */ package net.sourceforge.stripes.ajax; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import net.sourceforge.stripes.action.Resolution; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - /** - * Resolution that will convert a Java object web to a web of JavaScript objects and arrays, and - * stream the JavaScript back to the client. The output of this resolution can be evaluated in + * Resolution that will convert a Java object web to a web of JavaScript objects and arrays, and + * stream the JavaScript back to the client. The output of this resolution can be evaluated in * JavaScript using the eval() function, and will return a reference to the top level JavaScript - * object. For more information see {@link JavaScriptBuilder}
+ * object. For more information see {@link JavaScriptBuilder} * * @author Tim Fennell * @since Stripes 1.1 */ public class JavaScriptResolution implements Resolution { - private JavaScriptBuilder builder; + private JavaScriptBuilder builder; - /** - * Constructs a new JavaScriptResolution that will convert the supplied object to JavaScript. - * - * @param rootObject an Object of any type supported by {@link JavaScriptBuilder}. In most cases - * this will either be a JavaBean, Map, Collection or Array, but may also be any one of - * the basic Java types including String, Date, Number etc. - * @param objectsToExclude Classes and/or property names to exclude from the output. - */ - public JavaScriptResolution(Object rootObject, Object... objectsToExclude) { - this.builder = new JavaScriptBuilder(rootObject, objectsToExclude); - } + /** + * Constructs a new JavaScriptResolution that will convert the supplied object to JavaScript. + * + * @param rootObject an Object of any type supported by {@link JavaScriptBuilder}. In most cases + * this will either be a JavaBean, Map, Collection or Array, but may also be any one of the + * basic Java types including String, Date, Number etc. + * @param objectsToExclude Classes and/or property names to exclude from the output. + */ + public JavaScriptResolution(Object rootObject, Object... objectsToExclude) { + this.builder = new JavaScriptBuilder(rootObject, objectsToExclude); + } - /** - * Adds one or more properties to the list of types to exclude when translating - * to JavaScript. - * - * @param property one or more property names to exclude - * @return the JavaScripResolution instance to simplify method chaining - */ - public JavaScriptResolution addPropertyExclusion(final String... property) { - this.builder.addPropertyExclusion(property); - return this; - } + /** + * Adds one or more properties to the list of types to exclude when translating to JavaScript. + * + * @param property one or more property names to exclude + * @return the JavaScripResolution instance to simplify method chaining + */ + public JavaScriptResolution addPropertyExclusion(final String... property) { + this.builder.addPropertyExclusion(property); + return this; + } - /** - * Adds one or more classes to the list of types to exclude when translating - * to JavaScript. - * - * @param clazz one or more classes to exclude - * @return the JavaScripResolution instance to simplify method chaining - */ - public JavaScriptResolution addClassExclusion(final Class>... clazz) { - this.builder.addClassExclusion(clazz); - return this; - } + /** + * Adds one or more classes to the list of types to exclude when translating to JavaScript. + * + * @param clazz one or more classes to exclude + * @return the JavaScripResolution instance to simplify method chaining + */ + public JavaScriptResolution addClassExclusion(final Class>... clazz) { + this.builder.addClassExclusion(clazz); + return this; + } - /** - * Converts the object passed in to JavaScript and streams it back to the client. - */ - public void execute(HttpServletRequest request, HttpServletResponse response) throws Exception { - response.setContentType("text/javascript"); - this.builder.build(response.getWriter()); - response.flushBuffer(); - } + /** Converts the object passed in to JavaScript and streams it back to the client. */ + public void execute(HttpServletRequest request, HttpServletResponse response) throws Exception { + response.setContentType("text/javascript"); + this.builder.build(response.getWriter()); + response.flushBuffer(); + } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/config/BootstrapPropertyResolver.java b/stripes/src/main/java/net/sourceforge/stripes/config/BootstrapPropertyResolver.java index dbd1b0639..2cd09fe22 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/config/BootstrapPropertyResolver.java +++ b/stripes/src/main/java/net/sourceforge/stripes/config/BootstrapPropertyResolver.java @@ -14,6 +14,7 @@ */ package net.sourceforge.stripes.config; +import jakarta.servlet.FilterConfig; import java.lang.reflect.Modifier; import java.security.AccessControlException; import java.util.ArrayList; @@ -21,8 +22,6 @@ import java.util.Iterator; import java.util.List; import java.util.Set; -import javax.servlet.FilterConfig; - import net.sourceforge.stripes.exception.StripesRuntimeException; import net.sourceforge.stripes.util.Log; import net.sourceforge.stripes.util.ReflectUtil; @@ -31,249 +30,257 @@ import net.sourceforge.stripes.vfs.VFS; /** - *Resolves configuration properties that are used to bootstrap the system. Essentially this boils - * down to a handful of properties that are needed to figure out which configuration class should - * be instantiated, and any values needed by that configuration class to locate configuration - * information.
+ * Resolves configuration properties that are used to bootstrap the system. Essentially this boils + * down to a handful of properties that are needed to figure out which configuration class should be + * instantiated, and any values needed by that configuration class to locate configuration + * information. * *Properties are looked for in the following order: - *
- *
- *- Initialization Parameters for the Dispatcher servlet
- *- Initialization Parameters for the Servlet Context
- *- Java System Properties
- *+ *
* * @author Tim Fennell */ public class BootstrapPropertyResolver { - private static final Log log = Log.getInstance(BootstrapPropertyResolver.class); - - private FilterConfig filterConfig; + private static final Log log = Log.getInstance(BootstrapPropertyResolver.class); - /** The Configuration Key for looking up the comma separated list of VFS classes. */ - public static final String VFS_CLASSES = "VFS.Classes"; + private FilterConfig filterConfig; - /** The Configuration Key for looking up the comma separated list of extension packages. */ - public static final String PACKAGES = "Extension.Packages"; + /** The Configuration Key for looking up the comma separated list of VFS classes. */ + public static final String VFS_CLASSES = "VFS.Classes"; - /** Constructs a new BootstrapPropertyResolver with the given ServletConfig. */ - public BootstrapPropertyResolver(FilterConfig filterConfig) { - setFilterConfig(filterConfig); - initVFS(); - } + /** The Configuration Key for looking up the comma separated list of extension packages. */ + public static final String PACKAGES = "Extension.Packages"; + + /** Constructs a new BootstrapPropertyResolver with the given ServletConfig. */ + public BootstrapPropertyResolver(FilterConfig filterConfig) { + setFilterConfig(filterConfig); + initVFS(); + } - /** Stores a reference to the filter's FilterConfig object. */ - public void setFilterConfig(FilterConfig filterConfig) { - this.filterConfig = filterConfig; + /** Stores a reference to the filter's FilterConfig object. */ + public void setFilterConfig(FilterConfig filterConfig) { + this.filterConfig = filterConfig; + } + + /** Returns a reference to the StripesFilter's FilterConfig object. */ + public FilterConfig getFilterConfig() { + return this.filterConfig; + } + + /** Add {@link VFS} implementations that are specified in the filter configuration. */ + @SuppressWarnings("unchecked") + protected void initVFS() { + List- Initialization Parameters for the Dispatcher servlet + *
- Initialization Parameters for the Servlet Context + *
- Java System Properties + *
> vfsImpls = getClassPropertyList(VFS_CLASSES); + for (Class> clazz : vfsImpls) { + if (!VFS.class.isAssignableFrom(clazz)) + log.warn("Class ", clazz.getName(), " does not extend ", VFS.class.getName()); + else VFS.addImplClass((Class extends VFS>) clazz); } + } + + /** + * Fetches a configuration property in the manner described in the class level javadoc for this + * class. + * + * @param key the String name of the configuration value to be looked up + * @return String the value of the configuration item or null + */ + public String getProperty(String key) { + String value = null; - /** Returns a reference to the StripesFilter's FilterConfig object. */ - public FilterConfig getFilterConfig() { - return this.filterConfig; + try { + value = this.filterConfig.getInitParameter(key); + } catch (AccessControlException e) { + log.debug( + "Security manager prevented " + + getClass().getName() + + " from reading filter init-param" + + key); } - /** Add {@link VFS} implementations that are specified in the filter configuration. */ - @SuppressWarnings("unchecked") - protected void initVFS() { - List > vfsImpls = getClassPropertyList(VFS_CLASSES); - for (Class> clazz : vfsImpls) { - if (!VFS.class.isAssignableFrom(clazz)) - log.warn("Class ", clazz.getName(), " does not extend ", VFS.class.getName()); - else - VFS.addImplClass((Class extends VFS>) clazz); - } + if (value == null) { + try { + value = this.filterConfig.getServletContext().getInitParameter(key); + } catch (AccessControlException e) { + log.debug( + "Security manager prevented " + + getClass().getName() + + " from reading servlet context init-param" + + key); + } } - /** - * Fetches a configuration property in the manner described in the class level javadoc for - * this class. - * - * @param key the String name of the configuration value to be looked up - * @return String the value of the configuration item or null - */ - public String getProperty(String key) { - String value = null; + if (value == null) { + try { + value = System.getProperty(key); + } catch (AccessControlException e) { + log.debug( + "Security manager prevented " + + getClass().getName() + + " from reading system property " + + key); + } + } - try { - value = this.filterConfig.getInitParameter(key); - } - catch (AccessControlException e) { - log.debug("Security manager prevented " + getClass().getName() - + " from reading filter init-param" + key); - } + return value; + } - if (value == null) { - try { - value = this.filterConfig.getServletContext().getInitParameter(key); - } - catch (AccessControlException e) { - log.debug("Security manager prevented " + getClass().getName() - + " from reading servlet context init-param" + key); - } - } + /** + * Attempts to find a class the user has specified in web.xml or by auto-discovery in packages + * listed in web.xml under Extension.Packages. Classes specified in web.xml take precedence. + * + * @param paramName the parameter to look for in web.xml + * @param targetType the type that we're looking for + * @return the Class that was found + */ + @SuppressWarnings("unchecked") + public Class extends T> getClassProperty(String paramName, Class targetType) { + Class extends T> clazz = null; - if (value == null) { - try { - value = System.getProperty(key); - } - catch (AccessControlException e) { - log.debug("Security manager prevented " + getClass().getName() - + " from reading system property " + key); - } - } + String className = getProperty(paramName); - return value; + if (className != null) { + // web.xml takes precedence + try { + clazz = ReflectUtil.findClass(className); + log.info( + "Class implementing/extending ", + targetType.getSimpleName(), + " found in web.xml: ", + className); + } catch (ClassNotFoundException e) { + log.error( + "Couldn't find class specified in web.xml under param ", paramName, ": ", className); + } + } else { + // we didn't find it in web.xml so now we check any extension packages + ResolverUtil resolver = new ResolverUtil (); + String[] packages = StringUtil.standardSplit(getProperty(PACKAGES)); + resolver.findImplementations(targetType, packages); + Set > classes = resolver.getClasses(); + removeDontAutoloadClasses(classes); + removeAbstractClasses(classes); + if (classes.size() == 1) { + clazz = classes.iterator().next(); + className = clazz.getName(); + log.info( + "Class implementing/extending ", + targetType.getSimpleName(), + " found via auto-discovery: ", + className); + } else if (classes.size() > 1) { + throw new StripesRuntimeException( + StringUtil.combineParts( + "Found too many classes implementing/extending ", + targetType.getSimpleName(), + ": ", + classes)); + } } - - /** - * Attempts to find a class the user has specified in web.xml or by auto-discovery in packages - * listed in web.xml under Extension.Packages. Classes specified in web.xml take precedence. - * - * @param paramName the parameter to look for in web.xml - * @param targetType the type that we're looking for - * @return the Class that was found - */ - @SuppressWarnings("unchecked") - public Class extends T> getClassProperty(String paramName, Class targetType) - { - Class extends T> clazz = null; - String className = getProperty(paramName); + return clazz; + } - if (className != null) { - // web.xml takes precedence - try { - clazz = ReflectUtil.findClass(className); - log.info("Class implementing/extending ", targetType.getSimpleName(), - " found in web.xml: ", className); - } - catch (ClassNotFoundException e) { - log.error("Couldn't find class specified in web.xml under param ", paramName, ": ", - className); - } - } - else { - // we didn't find it in web.xml so now we check any extension packages - ResolverUtil resolver = new ResolverUtil (); - String[] packages = StringUtil.standardSplit(getProperty(PACKAGES)); - resolver.findImplementations(targetType, packages); - Set > classes = resolver.getClasses(); - removeDontAutoloadClasses(classes); - removeAbstractClasses(classes); - if (classes.size() == 1) { - clazz = classes.iterator().next(); - className = clazz.getName(); - log.info("Class implementing/extending ", targetType.getSimpleName(), - " found via auto-discovery: ", className); - } - else if (classes.size() > 1) { - throw new StripesRuntimeException(StringUtil.combineParts( - "Found too many classes implementing/extending ", targetType - .getSimpleName(), ": ", classes)); - } - } - - return clazz; - } - - /** - * Attempts to find all classes the user has specified in web.xml. - * - * @param paramName the parameter to look for in web.xml - * @return a List of classes found - */ - public List > getClassPropertyList(String paramName) - { - List > classes = new ArrayList >(); + /** + * Attempts to find all classes the user has specified in web.xml. + * + * @param paramName the parameter to look for in web.xml + * @return a List of classes found + */ + public List > getClassPropertyList(String paramName) { + List > classes = new ArrayList >(); - String classList = getProperty(paramName); + String classList = getProperty(paramName); - if (classList != null) { - String[] classNames = StringUtil.standardSplit(classList); - for (String className : classNames) { - className = className.trim(); - try { - classes.add(ReflectUtil.findClass(className)); - } - catch (ClassNotFoundException e) { - throw new StripesRuntimeException("Could not find class [" + className - + "] specified by the configuration parameter [" + paramName - + "]. This value must contain fully qualified class names separated " - + " by commas."); - } - } + if (classList != null) { + String[] classNames = StringUtil.standardSplit(classList); + for (String className : classNames) { + className = className.trim(); + try { + classes.add(ReflectUtil.findClass(className)); + } catch (ClassNotFoundException e) { + throw new StripesRuntimeException( + "Could not find class [" + + className + + "] specified by the configuration parameter [" + + paramName + + "]. This value must contain fully qualified class names separated " + + " by commas."); } - - return classes; - } - - /** - * Attempts to find classes by auto-discovery in packages listed in web.xml under - * Extension.Packages. - * - * @param targetType the type that we're looking for - * @return a List of classes found - */ - public List > getClassPropertyList(Class targetType) - { - ResolverUtil resolver = new ResolverUtil (); - String[] packages = StringUtil.standardSplit(getProperty(PACKAGES)); - resolver.findImplementations(targetType, packages); - Set > classes = resolver.getClasses(); - removeDontAutoloadClasses(classes); - removeAbstractClasses(classes); - return new ArrayList >(classes); + } } - /** - * Attempts to find all matching classes the user has specified in web.xml or by auto-discovery - * in packages listed in web.xml under Extension.Packages. - * - * @param paramName the parameter to look for in web.xml - * @param targetType the type that we're looking for - * @return the Class that was found - */ - @SuppressWarnings("unchecked") - public List > getClassPropertyList(String paramName, Class targetType) - { - List > classes = new ArrayList >(); + return classes; + } - for (Class> clazz : getClassPropertyList(paramName)) { - // can't use addAll :( - classes.add((Class extends T>) clazz); - } + /** + * Attempts to find classes by auto-discovery in packages listed in web.xml under + * Extension.Packages. + * + * @param targetType the type that we're looking for + * @return a List of classes found + */ + public List > getClassPropertyList(Class targetType) { + ResolverUtil resolver = new ResolverUtil (); + String[] packages = StringUtil.standardSplit(getProperty(PACKAGES)); + resolver.findImplementations(targetType, packages); + Set > classes = resolver.getClasses(); + removeDontAutoloadClasses(classes); + removeAbstractClasses(classes); + return new ArrayList >(classes); + } - classes.addAll(getClassPropertyList(targetType)); + /** + * Attempts to find all matching classes the user has specified in web.xml or by auto-discovery in + * packages listed in web.xml under Extension.Packages. + * + * @param paramName the parameter to look for in web.xml + * @param targetType the type that we're looking for + * @return the Class that was found + */ + @SuppressWarnings("unchecked") + public List > getClassPropertyList(String paramName, Class targetType) { + List > classes = new ArrayList >(); - return classes; + for (Class> clazz : getClassPropertyList(paramName)) { + // can't use addAll :( + classes.add((Class extends T>) clazz); } - /** Removes any classes from the collection that are marked with {@link DontAutoLoad}. */ - protected void removeDontAutoloadClasses(Collection > classes) { - Iterator > iterator = classes.iterator(); - while (iterator.hasNext()) { - Class extends T> clazz = iterator.next(); - if (clazz.isAnnotationPresent(DontAutoLoad.class)) { - log.debug("Ignoring ", clazz, " because @DontAutoLoad is present."); - iterator.remove(); - } - } + classes.addAll(getClassPropertyList(targetType)); + + return classes; + } + + /** Removes any classes from the collection that are marked with {@link DontAutoLoad}. */ + protected void removeDontAutoloadClasses(Collection > classes) { + Iterator > iterator = classes.iterator(); + while (iterator.hasNext()) { + Class extends T> clazz = iterator.next(); + if (clazz.isAnnotationPresent(DontAutoLoad.class)) { + log.debug("Ignoring ", clazz, " because @DontAutoLoad is present."); + iterator.remove(); + } } - - /** Removes any classes from the collection that are abstract or interfaces. */ - protected void removeAbstractClasses(Collection > classes) { - Iterator > iterator = classes.iterator(); - while (iterator.hasNext()) { - Class extends T> clazz = iterator.next(); - if (clazz.isInterface()) { - log.trace("Ignoring ", clazz, " because it is an interface."); - iterator.remove(); - } - else if ((clazz.getModifiers() & Modifier.ABSTRACT) == Modifier.ABSTRACT) { - log.trace("Ignoring ", clazz, " because it is abstract."); - iterator.remove(); - } - } + } + + /** Removes any classes from the collection that are abstract or interfaces. */ + protected void removeAbstractClasses(Collection > classes) { + Iterator > iterator = classes.iterator(); + while (iterator.hasNext()) { + Class extends T> clazz = iterator.next(); + if (clazz.isInterface()) { + log.trace("Ignoring ", clazz, " because it is an interface."); + iterator.remove(); + } else if ((clazz.getModifiers() & Modifier.ABSTRACT) == Modifier.ABSTRACT) { + log.trace("Ignoring ", clazz, " because it is abstract."); + iterator.remove(); + } } + } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/config/ConfigurableComponent.java b/stripes/src/main/java/net/sourceforge/stripes/config/ConfigurableComponent.java index c5edafd27..dffcb387e 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/config/ConfigurableComponent.java +++ b/stripes/src/main/java/net/sourceforge/stripes/config/ConfigurableComponent.java @@ -15,22 +15,22 @@ package net.sourceforge.stripes.config; /** - * Interface which is extended by all the major configurable chunks of Stripes. Allows a + * Interface which is extended by all the major configurable chunks of Stripes. Allows a * Configuration to instantiate and pass configuration to each of the main components in a - * standardized manner. It is expected that all ConfigurableComponents will have a public - * no-arg constructor. + * standardized manner. It is expected that all ConfigurableComponents will have a public no-arg + * constructor. * * @author Tim Fennell */ public interface ConfigurableComponent { - /** - * Invoked directly after instantiation to allow the configured component to perform - * one time initialization. Components are expected to fail loudly if they are not - * going to be in a valid state after initialization. - * - * @param configuration the Configuration object being used by Stripes - * @throws Exception should be thrown if the component cannot be configured well enough to use. - */ - void init(Configuration configuration) throws Exception; + /** + * Invoked directly after instantiation to allow the configured component to perform one time + * initialization. Components are expected to fail loudly if they are not going to be in a valid + * state after initialization. + * + * @param configuration the Configuration object being used by Stripes + * @throws Exception should be thrown if the component cannot be configured well enough to use. + */ + void init(Configuration configuration) throws Exception; } diff --git a/stripes/src/main/java/net/sourceforge/stripes/config/Configuration.java b/stripes/src/main/java/net/sourceforge/stripes/config/Configuration.java index 9bfb24fb0..163c396e8 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/config/Configuration.java +++ b/stripes/src/main/java/net/sourceforge/stripes/config/Configuration.java @@ -14,200 +14,197 @@ */ package net.sourceforge.stripes.config; +import jakarta.servlet.ServletContext; +import java.util.Collection; +import net.sourceforge.stripes.controller.ActionBeanContextFactory; import net.sourceforge.stripes.controller.ActionBeanPropertyBinder; import net.sourceforge.stripes.controller.ActionResolver; -import net.sourceforge.stripes.controller.ActionBeanContextFactory; -import net.sourceforge.stripes.controller.ObjectFactory; -import net.sourceforge.stripes.localization.LocalizationBundleFactory; -import net.sourceforge.stripes.localization.LocalePicker; -import net.sourceforge.stripes.validation.TypeConverterFactory; -import net.sourceforge.stripes.validation.ValidationMetadataProvider; -import net.sourceforge.stripes.tag.TagErrorRendererFactory; -import net.sourceforge.stripes.tag.PopulationStrategy; -import net.sourceforge.stripes.format.FormatterFactory; import net.sourceforge.stripes.controller.Interceptor; import net.sourceforge.stripes.controller.LifecycleStage; +import net.sourceforge.stripes.controller.ObjectFactory; import net.sourceforge.stripes.controller.multipart.MultipartWrapperFactory; import net.sourceforge.stripes.exception.ExceptionHandler; - -import javax.servlet.ServletContext; -import java.util.Collection; +import net.sourceforge.stripes.format.FormatterFactory; +import net.sourceforge.stripes.localization.LocalePicker; +import net.sourceforge.stripes.localization.LocalizationBundleFactory; +import net.sourceforge.stripes.tag.PopulationStrategy; +import net.sourceforge.stripes.tag.TagErrorRendererFactory; +import net.sourceforge.stripes.validation.TypeConverterFactory; +import net.sourceforge.stripes.validation.ValidationMetadataProvider; /** - * Type safe interface for accessing configuration information used to configure Stripes. All - * Configuration implementations are handed a reference to the BootstrapPropertyResolver to - * enable them to find initial values and fully initialize themselves. Through the + * Type safe interface for accessing configuration information used to configure Stripes. All + * Configuration implementations are handed a reference to the BootstrapPropertyResolver to enable + * them to find initial values and fully initialize themselves. Through the * BootstrapPropertyResolver implementations also get access to the ServletConfig of the - * DispatcherServlet which can be used for locating configuration values if desired.
+ * DispatcherServlet which can be used for locating configuration values if desired. * - *Implementations of Configuration should fail fast. At initialization time they should - * detect as many failures as possible and raise an exception. Since exceptions in Configuration - * are considered fatal there are no exception specifications and implementations are expected to - * throw runtime exceptions with plenty of details about the failure and its suspected cause(s).
+ *Implementations of Configuration should fail fast. At initialization time they should detect + * as many failures as possible and raise an exception. Since exceptions in Configuration are + * considered fatal there are no exception specifications and implementations are expected to throw + * runtime exceptions with plenty of details about the failure and its suspected cause(s). * * @author Tim Fennell */ public interface Configuration { - /** - * Supplies the Configuration with a BootstrapPropertyResolver. This method is guaranteed to - * be invoked prior to the init method. - * - * @param resolver a BootStrapPropertyResolver which can be used to find any values required - * by the Configuration in order to initialize - */ - void setBootstrapPropertyResolver(BootstrapPropertyResolver resolver); - - /** - * Called by the DispatcherServlet to initialize the Configuration. Any operations which may - * fail and cause the Configuration to be inaccessible should be performed here (e.g. - * opening a configuration file and reading the contents). - */ - void init(); - - /** - * Implementations should implement this method to simply return a reference to the - * BootstrapPropertyResolver passed to the Configuration at initialization time. - * - * @return BootstrapPropertyResolver the instance passed to the init() method - */ - BootstrapPropertyResolver getBootstrapPropertyResolver(); - - /** - * Retrieves the ServletContext for the context within which the Stripes application - * is executing. - * - * @return the ServletContext in which the application is running - */ - ServletContext getServletContext(); - - /** Enable or disable debug mode. */ - void setDebugMode(boolean debugMode); - - /** Returns true if the Stripes application is running in debug mode. */ - boolean isDebugMode(); - - /** - * Returns an instance of {@link ObjectFactory} that is used throughout Stripes to instantiate - * classes. - * - * @return an instance of {@link ObjectFactory}. - */ - ObjectFactory getObjectFactory(); - - /** - * Returns an instance of ActionResolver that will be used by Stripes to lookup and resolve - * ActionBeans. The instance should be cached by the Configuration since multiple entities - * in the system may access the ActionResolver throughout the lifetime of the application. - * - * @return the Class representing the configured ActionResolver - */ - ActionResolver getActionResolver(); - - /** - * Returns an instance of ActionBeanPropertyBinder that is responsible for binding all - * properties to all ActionBeans at runtime. The instance should be cached by the Configuration - * since multiple entities in the system may access the ActionBeanPropertyBinder throughout the - * lifetime of the application. - * - * @return ActionBeanPropertyBinder the property binder to be used by Stripes - */ - ActionBeanPropertyBinder getActionBeanPropertyBinder(); - - /** - * Returns an instance of TypeConverterFactory that is responsible for providing lookups and - * instances of TypeConverters for the validation system. The instance should be cached by the - * Configuration since multiple entities in the system may access the TypeConverterFactory - * throughout the lifetime of the application. - * - * @return TypeConverterFactory an instance of a TypeConverterFactory implementation - */ - TypeConverterFactory getTypeConverterFactory(); - - /** - * Returns an instance of LocalizationBundleFactory that is responsible for looking up - * resource bundles for the varying localization needs of a web application. The instance should - * be cached by the Configuration since multiple entities in the system may access the - * LocalizationBundleFactory throughout the lifetime of the application. - * - * @return LocalizationBundleFactory an instance of a LocalizationBundleFactory implementation - */ - LocalizationBundleFactory getLocalizationBundleFactory(); - - /** - * Returns an instance of LocalePicker that is responsible for choosing the Locale for - * each request that enters the system. - * - * @return LocalePicker an instance of a LocalePicker implementation - */ - LocalePicker getLocalePicker(); - - /** - * Returns an instance of FormatterFactory that is responsible for creating Formatter objects - * for converting rich types into Strings for display on pages. - * - * @return LocalePicker an instance of a LocalePicker implementation - */ - FormatterFactory getFormatterFactory(); - - /** - * Returns an instance of a tag error renderer factory for building custom error renderers - * for form input tags that have field errors. - * - * @return TagErrorRendererFactory an instance of TagErrorRendererFactory - */ - TagErrorRendererFactory getTagErrorRendererFactory(); - - /** - * Returns an instance of a PopulationStrategy that determines from where a tag's value - * should be repopulated. - * - * @return PopulationStrategy an instance of PopulationStrategy - */ - PopulationStrategy getPopulationStrategy(); - - /** - * Returns an instance of an action bean context factory which will used throughout Stripes - * to manufacture ActionBeanContext objects. This allows projects to extend ActionBeanContext - * and provide additional type safe methods for accessing contextual information cleanly. - * - * @return ActionBeanContextFactory an instance of ActionBeanContextFactory - */ - ActionBeanContextFactory getActionBeanContextFactory(); - - /** - * Fetches the interceptors that should be executed around the lifecycle stage applied. - * Must return a non-null collection, but the collection may be empty. The Interceptors - * are invoked around the code which executes the given lifecycle function (e.g. - * ActionBeanResolution), and as a result can execute code both before and after it. - * - * @return Collection
* - *an ordered collection of interceptors to be executed - * around the given lifecycle stage. - */ - Collection getInterceptors(LifecycleStage stage); - - /** - * Returns an instance of ExceptionHandler that can be used by Stripes to handle any - * exceptions that arise as the result of processing a request. - * - * @return ExceptionHandler an instance of ExceptionHandler - */ - ExceptionHandler getExceptionHandler(); - - /** - * Returns an instance of MultipartWrapperFactory that can be used by Stripes to construct - * MultipartWrapper instances for dealing with multipart requests (those containing file - * uploads). - * - * @return MultipartWrapperFactory an instance of the wrapper factory - */ - MultipartWrapperFactory getMultipartWrapperFactory(); - - /** - * Returns an instance of {@link ValidationMetadataProvider} that can be used by Stripes to - * determine what validations need to be applied during - * {@link LifecycleStage#BindingAndValidation}. - * - * @return an instance of {@link ValidationMetadataProvider} - */ - ValidationMetadataProvider getValidationMetadataProvider(); + /** + * Supplies the Configuration with a BootstrapPropertyResolver. This method is guaranteed to be + * invoked prior to the init method. + * + * @param resolver a BootStrapPropertyResolver which can be used to find any values required by + * the Configuration in order to initialize + */ + void setBootstrapPropertyResolver(BootstrapPropertyResolver resolver); + + /** + * Called by the DispatcherServlet to initialize the Configuration. Any operations which may fail + * and cause the Configuration to be inaccessible should be performed here (e.g. opening a + * configuration file and reading the contents). + */ + void init(); + + /** + * Implementations should implement this method to simply return a reference to the + * BootstrapPropertyResolver passed to the Configuration at initialization time. + * + * @return BootstrapPropertyResolver the instance passed to the init() method + */ + BootstrapPropertyResolver getBootstrapPropertyResolver(); + + /** + * Retrieves the ServletContext for the context within which the Stripes application is executing. + * + * @return the ServletContext in which the application is running + */ + ServletContext getServletContext(); + + /** Enable or disable debug mode. */ + void setDebugMode(boolean debugMode); + + /** Returns true if the Stripes application is running in debug mode. */ + boolean isDebugMode(); + + /** + * Returns an instance of {@link ObjectFactory} that is used throughout Stripes to instantiate + * classes. + * + * @return an instance of {@link ObjectFactory}. + */ + ObjectFactory getObjectFactory(); + + /** + * Returns an instance of ActionResolver that will be used by Stripes to lookup and resolve + * ActionBeans. The instance should be cached by the Configuration since multiple entities in the + * system may access the ActionResolver throughout the lifetime of the application. + * + * @return the Class representing the configured ActionResolver + */ + ActionResolver getActionResolver(); + + /** + * Returns an instance of ActionBeanPropertyBinder that is responsible for binding all properties + * to all ActionBeans at runtime. The instance should be cached by the Configuration since + * multiple entities in the system may access the ActionBeanPropertyBinder throughout the lifetime + * of the application. + * + * @return ActionBeanPropertyBinder the property binder to be used by Stripes + */ + ActionBeanPropertyBinder getActionBeanPropertyBinder(); + + /** + * Returns an instance of TypeConverterFactory that is responsible for providing lookups and + * instances of TypeConverters for the validation system. The instance should be cached by the + * Configuration since multiple entities in the system may access the TypeConverterFactory + * throughout the lifetime of the application. + * + * @return TypeConverterFactory an instance of a TypeConverterFactory implementation + */ + TypeConverterFactory getTypeConverterFactory(); + + /** + * Returns an instance of LocalizationBundleFactory that is responsible for looking up resource + * bundles for the varying localization needs of a web application. The instance should be cached + * by the Configuration since multiple entities in the system may access the + * LocalizationBundleFactory throughout the lifetime of the application. + * + * @return LocalizationBundleFactory an instance of a LocalizationBundleFactory implementation + */ + LocalizationBundleFactory getLocalizationBundleFactory(); + + /** + * Returns an instance of LocalePicker that is responsible for choosing the Locale for each + * request that enters the system. + * + * @return LocalePicker an instance of a LocalePicker implementation + */ + LocalePicker getLocalePicker(); + + /** + * Returns an instance of FormatterFactory that is responsible for creating Formatter objects for + * converting rich types into Strings for display on pages. + * + * @return LocalePicker an instance of a LocalePicker implementation + */ + FormatterFactory getFormatterFactory(); + + /** + * Returns an instance of a tag error renderer factory for building custom error renderers for + * form input tags that have field errors. + * + * @return TagErrorRendererFactory an instance of TagErrorRendererFactory + */ + TagErrorRendererFactory getTagErrorRendererFactory(); + + /** + * Returns an instance of a PopulationStrategy that determines from where a tag's value should be + * repopulated. + * + * @return PopulationStrategy an instance of PopulationStrategy + */ + PopulationStrategy getPopulationStrategy(); + + /** + * Returns an instance of an action bean context factory which will used throughout Stripes to + * manufacture ActionBeanContext objects. This allows projects to extend ActionBeanContext and + * provide additional type safe methods for accessing contextual information cleanly. + * + * @return ActionBeanContextFactory an instance of ActionBeanContextFactory + */ + ActionBeanContextFactory getActionBeanContextFactory(); + + /** + * Fetches the interceptors that should be executed around the lifecycle stage applied. Must + * return a non-null collection, but the collection may be empty. The Interceptors are invoked + * around the code which executes the given lifecycle function (e.g. ActionBeanResolution), and as + * a result can execute code both before and after it. + * + * @return Collection an ordered collection of interceptors to be executed around the + * given lifecycle stage. + */ + Collection getInterceptors(LifecycleStage stage); + + /** + * Returns an instance of ExceptionHandler that can be used by Stripes to handle any exceptions + * that arise as the result of processing a request. + * + * @return ExceptionHandler an instance of ExceptionHandler + */ + ExceptionHandler getExceptionHandler(); + + /** + * Returns an instance of MultipartWrapperFactory that can be used by Stripes to construct + * MultipartWrapper instances for dealing with multipart requests (those containing file uploads). + * + * @return MultipartWrapperFactory an instance of the wrapper factory + */ + MultipartWrapperFactory getMultipartWrapperFactory(); + + /** + * Returns an instance of {@link ValidationMetadataProvider} that can be used by Stripes to + * determine what validations need to be applied during {@link + * LifecycleStage#BindingAndValidation}. + * + * @return an instance of {@link ValidationMetadataProvider} + */ + ValidationMetadataProvider getValidationMetadataProvider(); } diff --git a/stripes/src/main/java/net/sourceforge/stripes/config/DefaultConfiguration.java b/stripes/src/main/java/net/sourceforge/stripes/config/DefaultConfiguration.java index 0a9cf4996..ee6cd3c90 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/config/DefaultConfiguration.java +++ b/stripes/src/main/java/net/sourceforge/stripes/config/DefaultConfiguration.java @@ -14,6 +14,7 @@ */ package net.sourceforge.stripes.config; +import jakarta.servlet.ServletContext; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -23,9 +24,6 @@ import java.util.List; import java.util.Map; import java.util.Set; - -import javax.servlet.ServletContext; - import net.sourceforge.stripes.controller.ActionBeanContextFactory; import net.sourceforge.stripes.controller.ActionBeanPropertyBinder; import net.sourceforge.stripes.controller.ActionResolver; @@ -62,454 +60,492 @@ import net.sourceforge.stripes.validation.ValidationMetadataProvider; /** - * Centralized location for defaults for all Configuration properties. This implementation does - * not lookup configuration information anywhere! It returns hard-coded defaults that will result - * in a working system without any user intervention.
+ * Centralized location for defaults for all Configuration properties. This implementation does not + * lookup configuration information anywhere! It returns hard-coded defaults that will result in a + * working system without any user intervention. * *Despite it's name the DefaultConfiguration is not in fact the default Configuration - * implementation in Stripes! Instead it is the retainer of default configuration values. The - * Configuration implementation that is used when no alternative is configured is the - * {@link RuntimeConfiguration}, which is a direct subclass of DefaultConfiguration, and when no - * further configuration properties are supplied behaves identically to the DefaultConfiguration.
+ * implementation in Stripes! Instead it is the retainer of default configuration values. The + * Configuration implementation that is used when no alternative is configured is the {@link + * RuntimeConfiguration}, which is a direct subclass of DefaultConfiguration, and when no further + * configuration properties are supplied behaves identically to the DefaultConfiguration. * - *The DefaultConfiguration is designed to be easily extended as needed. The init() method + *
The DefaultConfiguration is designed to be easily extended as needed. The init() method * ensures that components are initialized in the correct order (taking dependencies into account), * and should generally not be overridden. It invokes a number of initXXX() methods, one per * configurable component. Subclasses should override any of the initXXX() methods desirable to * return a fully initialized instance of the relevant component type, or null if the default is - * desired.
+ * desired. * * @author Tim Fennell */ public class DefaultConfiguration implements Configuration { - /** Log implementation for use within this class. */ - private static final Log log = Log.getInstance(DefaultConfiguration.class); - - private boolean debugMode; - private BootstrapPropertyResolver resolver; - private ObjectFactory objectFactory; - private ActionResolver actionResolver; - private ActionBeanPropertyBinder actionBeanPropertyBinder; - private ActionBeanContextFactory actionBeanContextFactory; - private TypeConverterFactory typeConverterFactory; - private LocalizationBundleFactory localizationBundleFactory; - private LocalePicker localePicker; - private FormatterFactory formatterFactory; - private TagErrorRendererFactory tagErrorRendererFactory; - private PopulationStrategy populationStrategy; - private Map> interceptors; - private ExceptionHandler exceptionHandler; - private MultipartWrapperFactory multipartWrapperFactory; - private ValidationMetadataProvider validationMetadataProvider; - - /** Gratefully accepts the BootstrapPropertyResolver handed to the Configuration. */ - public void setBootstrapPropertyResolver(BootstrapPropertyResolver resolver) { - this.resolver = resolver; - } - - /** - * Creates and stores instances of the objects of the type that the Configuration is - * responsible for providing, except where subclasses have already provided instances. - */ - @SuppressWarnings("unchecked") - public void init() { - try { - Boolean debugMode = initDebugMode(); - if (debugMode != null) { - this.debugMode = debugMode; - } - else { - this.debugMode = false; - } - - this.objectFactory = initObjectFactory(); - if (this.objectFactory == null) { - this.objectFactory = new DefaultObjectFactory(); - this.objectFactory.init(this); - } - if (this.objectFactory instanceof DefaultObjectFactory) { - List > classes = getBootstrapPropertyResolver() - .getClassPropertyList(ObjectPostProcessor.class); - List instances = new ArrayList (); - for (Class extends ObjectPostProcessor> clazz : classes) { - log.debug("Instantiating object post-processor ", clazz); - instances.add(this.objectFactory.newInstance(clazz)); - } - for (ObjectPostProcessor pp : instances) { - ((DefaultObjectFactory) this.objectFactory).addPostProcessor(pp); - } - } - - this.actionResolver = initActionResolver(); - if (this.actionResolver == null) { - this.actionResolver = new NameBasedActionResolver(); - this.actionResolver.init(this); - } - - this.actionBeanPropertyBinder = initActionBeanPropertyBinder(); - if (this.actionBeanPropertyBinder == null) { - this.actionBeanPropertyBinder = new DefaultActionBeanPropertyBinder(); - this.actionBeanPropertyBinder.init(this); - } - - this.actionBeanContextFactory = initActionBeanContextFactory(); - if (this.actionBeanContextFactory == null) { - this.actionBeanContextFactory = new DefaultActionBeanContextFactory(); - this.actionBeanContextFactory.init(this); - } - - this.typeConverterFactory = initTypeConverterFactory(); - if (this.typeConverterFactory == null) { - this.typeConverterFactory = new DefaultTypeConverterFactory(); - this.typeConverterFactory.init(this); - } - - this.localizationBundleFactory = initLocalizationBundleFactory(); - if (this.localizationBundleFactory == null) { - this.localizationBundleFactory = new DefaultLocalizationBundleFactory(); - this.localizationBundleFactory.init(this); - } - - this.localePicker = initLocalePicker(); - if (this.localePicker == null) { - this.localePicker = new DefaultLocalePicker(); - this.localePicker.init(this); - } - - this.formatterFactory = initFormatterFactory(); - if (this.formatterFactory == null) { - this.formatterFactory = new DefaultFormatterFactory(); - this.formatterFactory.init(this); - } - - this.tagErrorRendererFactory = initTagErrorRendererFactory(); - if (this.tagErrorRendererFactory == null) { - this.tagErrorRendererFactory = new DefaultTagErrorRendererFactory(); - this.tagErrorRendererFactory.init(this); - } - - this.populationStrategy = initPopulationStrategy(); - if (this.populationStrategy == null) { - this.populationStrategy = new BeanFirstPopulationStrategy(); - this.populationStrategy.init(this); - } - - this.exceptionHandler = initExceptionHandler(); - if (this.exceptionHandler == null) { - this.exceptionHandler = new DefaultExceptionHandler(); - this.exceptionHandler.init(this); - } - - this.multipartWrapperFactory = initMultipartWrapperFactory(); - if (this.multipartWrapperFactory == null) { - this.multipartWrapperFactory = new DefaultMultipartWrapperFactory(); - this.multipartWrapperFactory.init(this); - } - - this.validationMetadataProvider = initValidationMetadataProvider(); - if (this.validationMetadataProvider == null) { - this.validationMetadataProvider = new DefaultValidationMetadataProvider(); - this.validationMetadataProvider.init(this); - } - - this.interceptors = new HashMap >(); - Map > map = initCoreInterceptors(); - if (map != null) { - mergeInterceptorMaps(this.interceptors, map); - } - map = initInterceptors(); - if (map != null) { - mergeInterceptorMaps(this.interceptors, map); - } - - // do a quick check to see if any interceptor classes are configured more than once - for (Map.Entry > entry : this.interceptors.entrySet()) { - Set > classes = new HashSet >(); - Collection interceptors = entry.getValue(); - if (interceptors == null) - continue; - - for (Interceptor interceptor : interceptors) { - Class extends Interceptor> clazz = interceptor.getClass(); - if (classes.contains(clazz)) { - log.warn("Interceptor ", clazz, - " is configured to run more than once for ", entry.getKey()); - } - else { - classes.add(clazz); - } - } - } + /** Log implementation for use within this class. */ + private static final Log log = Log.getInstance(DefaultConfiguration.class); + + private boolean debugMode; + private BootstrapPropertyResolver resolver; + private ObjectFactory objectFactory; + private ActionResolver actionResolver; + private ActionBeanPropertyBinder actionBeanPropertyBinder; + private ActionBeanContextFactory actionBeanContextFactory; + private TypeConverterFactory typeConverterFactory; + private LocalizationBundleFactory localizationBundleFactory; + private LocalePicker localePicker; + private FormatterFactory formatterFactory; + private TagErrorRendererFactory tagErrorRendererFactory; + private PopulationStrategy populationStrategy; + private Map > interceptors; + private ExceptionHandler exceptionHandler; + private MultipartWrapperFactory multipartWrapperFactory; + private ValidationMetadataProvider validationMetadataProvider; + + /** Gratefully accepts the BootstrapPropertyResolver handed to the Configuration. */ + public void setBootstrapPropertyResolver(BootstrapPropertyResolver resolver) { + this.resolver = resolver; + } + + /** + * Creates and stores instances of the objects of the type that the Configuration is responsible + * for providing, except where subclasses have already provided instances. + */ + @SuppressWarnings("unchecked") + public void init() { + try { + Boolean debugMode = initDebugMode(); + if (debugMode != null) { + this.debugMode = debugMode; + } else { + this.debugMode = false; + } + + this.objectFactory = initObjectFactory(); + if (this.objectFactory == null) { + this.objectFactory = new DefaultObjectFactory(); + this.objectFactory.init(this); + } + if (this.objectFactory instanceof DefaultObjectFactory) { + List > classes = + getBootstrapPropertyResolver().getClassPropertyList(ObjectPostProcessor.class); + List instances = new ArrayList (); + for (Class extends ObjectPostProcessor> clazz : classes) { + log.debug("Instantiating object post-processor ", clazz); + instances.add(this.objectFactory.newInstance(clazz)); } - catch (Exception e) { - throw new StripesRuntimeException - ("Problem instantiating default configuration objects.", e); + for (ObjectPostProcessor pp : instances) { + ((DefaultObjectFactory) this.objectFactory).addPostProcessor(pp); } + } + + this.actionResolver = initActionResolver(); + if (this.actionResolver == null) { + this.actionResolver = new NameBasedActionResolver(); + this.actionResolver.init(this); + } + + this.actionBeanPropertyBinder = initActionBeanPropertyBinder(); + if (this.actionBeanPropertyBinder == null) { + this.actionBeanPropertyBinder = new DefaultActionBeanPropertyBinder(); + this.actionBeanPropertyBinder.init(this); + } + + this.actionBeanContextFactory = initActionBeanContextFactory(); + if (this.actionBeanContextFactory == null) { + this.actionBeanContextFactory = new DefaultActionBeanContextFactory(); + this.actionBeanContextFactory.init(this); + } + + this.typeConverterFactory = initTypeConverterFactory(); + if (this.typeConverterFactory == null) { + this.typeConverterFactory = new DefaultTypeConverterFactory(); + this.typeConverterFactory.init(this); + } + + this.localizationBundleFactory = initLocalizationBundleFactory(); + if (this.localizationBundleFactory == null) { + this.localizationBundleFactory = new DefaultLocalizationBundleFactory(); + this.localizationBundleFactory.init(this); + } + + this.localePicker = initLocalePicker(); + if (this.localePicker == null) { + this.localePicker = new DefaultLocalePicker(); + this.localePicker.init(this); + } + + this.formatterFactory = initFormatterFactory(); + if (this.formatterFactory == null) { + this.formatterFactory = new DefaultFormatterFactory(); + this.formatterFactory.init(this); + } + + this.tagErrorRendererFactory = initTagErrorRendererFactory(); + if (this.tagErrorRendererFactory == null) { + this.tagErrorRendererFactory = new DefaultTagErrorRendererFactory(); + this.tagErrorRendererFactory.init(this); + } + + this.populationStrategy = initPopulationStrategy(); + if (this.populationStrategy == null) { + this.populationStrategy = new BeanFirstPopulationStrategy(); + this.populationStrategy.init(this); + } + + this.exceptionHandler = initExceptionHandler(); + if (this.exceptionHandler == null) { + this.exceptionHandler = new DefaultExceptionHandler(); + this.exceptionHandler.init(this); + } + + this.multipartWrapperFactory = initMultipartWrapperFactory(); + if (this.multipartWrapperFactory == null) { + this.multipartWrapperFactory = new DefaultMultipartWrapperFactory(); + this.multipartWrapperFactory.init(this); + } + + this.validationMetadataProvider = initValidationMetadataProvider(); + if (this.validationMetadataProvider == null) { + this.validationMetadataProvider = new DefaultValidationMetadataProvider(); + this.validationMetadataProvider.init(this); + } + + this.interceptors = new HashMap >(); + Map > map = initCoreInterceptors(); + if (map != null) { + mergeInterceptorMaps(this.interceptors, map); + } + map = initInterceptors(); + if (map != null) { + mergeInterceptorMaps(this.interceptors, map); + } + + // do a quick check to see if any interceptor classes are configured more than once + for (Map.Entry > entry : + this.interceptors.entrySet()) { + Set > classes = new HashSet >(); + Collection interceptors = entry.getValue(); + if (interceptors == null) continue; + + for (Interceptor interceptor : interceptors) { + Class extends Interceptor> clazz = interceptor.getClass(); + if (classes.contains(clazz)) { + log.warn( + "Interceptor ", clazz, " is configured to run more than once for ", entry.getKey()); + } else { + classes.add(clazz); + } + } + } + } catch (Exception e) { + throw new StripesRuntimeException("Problem instantiating default configuration objects.", e); } - - /** Returns a reference to the resolver supplied at initialization time. */ - public BootstrapPropertyResolver getBootstrapPropertyResolver() { - return this.resolver; - } - - /** - * Retrieves the ServletContext for the context within which the Stripes application is - * executing. - * - * @return the ServletContext in which the application is running - */ - public ServletContext getServletContext() { - return getBootstrapPropertyResolver().getFilterConfig().getServletContext(); - } - - /** Enable or disable debug mode. */ - public void setDebugMode(boolean debugMode) { - this.debugMode = debugMode; - } - - /** Returns true if the Stripes application is running in debug mode. */ - public boolean isDebugMode() { - return debugMode; - } - - /** Allows subclasses to initialize a non-default debug mode value. */ - protected Boolean initDebugMode() { - return null; - } - - /** - * Returns an instance of {@link ObjectFactory} that is used throughout Stripes to instantiate - * classes. - * - * @return an instance of {@link ObjectFactory}. - */ - public ObjectFactory getObjectFactory() { - return this.objectFactory; - } - - /** Allows subclasses to initialize a non-default {@link ObjectFactory}. */ - protected ObjectFactory initObjectFactory() { return null; } - - /** - * Returns an instance of {@link NameBasedActionResolver} unless a subclass has - * overridden the default. - * @return ActionResolver an instance of the configured resolver - */ - public ActionResolver getActionResolver() { - return this.actionResolver; - } - - /** Allows subclasses to initialize a non-default ActionResovler. */ - protected ActionResolver initActionResolver() { return null; } - - /** - * Returns an instance of {@link DefaultActionBeanPropertyBinder} unless a subclass has - * overridden the default. - * @return ActionBeanPropertyBinder an instance of the configured binder - */ - public ActionBeanPropertyBinder getActionBeanPropertyBinder() { - return this.actionBeanPropertyBinder; - } - - /** Allows subclasses to initialize a non-default ActionBeanPropertyBinder. */ - protected ActionBeanPropertyBinder initActionBeanPropertyBinder() { return null; } - - /** - * Returns the configured ActionBeanContextFactory. Unless a subclass has configured a custom - * one, the instance will be a DefaultActionBeanContextFactory. - * - * @return ActionBeanContextFactory an instance of a factory for creating ActionBeanContexts - */ - public ActionBeanContextFactory getActionBeanContextFactory() { - return this.actionBeanContextFactory; - } - - /** Allows subclasses to initialize a non-default ActionBeanContextFactory. */ - protected ActionBeanContextFactory initActionBeanContextFactory() { return null; } - - /** - * Returns an instance of {@link DefaultTypeConverterFactory} unless a subclass has - * overridden the default.. - * @return TypeConverterFactory an instance of the configured factory. - */ - public TypeConverterFactory getTypeConverterFactory() { - return this.typeConverterFactory; - } - - /** Allows subclasses to initialize a non-default TypeConverterFactory. */ - protected TypeConverterFactory initTypeConverterFactory() { return null; } - - /** - * Returns an instance of a LocalizationBundleFactory. By default this will be an instance of - * DefaultLocalizationBundleFactory unless another type has been configured. - */ - public LocalizationBundleFactory getLocalizationBundleFactory() { - return this.localizationBundleFactory; - } - - /** Allows subclasses to initialize a non-default LocalizationBundleFactory. */ - protected LocalizationBundleFactory initLocalizationBundleFactory() { return null; } - - /** - * Returns an instance of a LocalePicker. Unless a subclass has picked another implementation - * will return an instance of DefaultLocalePicker. - */ - public LocalePicker getLocalePicker() { return this.localePicker; } - - /** Allows subclasses to initialize a non-default LocalePicker. */ - protected LocalePicker initLocalePicker() { return null; } - - /** - * Returns an instance of a FormatterFactory. Unless a subclass has picked another implementation - * will return an instance of DefaultFormatterFactory. - */ - public FormatterFactory getFormatterFactory() { return this.formatterFactory; } - - /** Allows subclasses to initialize a non-default FormatterFactory. */ - protected FormatterFactory initFormatterFactory() { return null; } - - /** - * Returns an instance of a TagErrorRendererFactory. Unless a subclass has picked another - * implementation, will return an instance of DefaultTagErrorRendererFactory. - */ - public TagErrorRendererFactory getTagErrorRendererFactory() { - return tagErrorRendererFactory; + } + + /** Returns a reference to the resolver supplied at initialization time. */ + public BootstrapPropertyResolver getBootstrapPropertyResolver() { + return this.resolver; + } + + /** + * Retrieves the ServletContext for the context within which the Stripes application is executing. + * + * @return the ServletContext in which the application is running + */ + public ServletContext getServletContext() { + return getBootstrapPropertyResolver().getFilterConfig().getServletContext(); + } + + /** Enable or disable debug mode. */ + public void setDebugMode(boolean debugMode) { + this.debugMode = debugMode; + } + + /** Returns true if the Stripes application is running in debug mode. */ + public boolean isDebugMode() { + return debugMode; + } + + /** Allows subclasses to initialize a non-default debug mode value. */ + protected Boolean initDebugMode() { + return null; + } + + /** + * Returns an instance of {@link ObjectFactory} that is used throughout Stripes to instantiate + * classes. + * + * @return an instance of {@link ObjectFactory}. + */ + public ObjectFactory getObjectFactory() { + return this.objectFactory; + } + + /** Allows subclasses to initialize a non-default {@link ObjectFactory}. */ + protected ObjectFactory initObjectFactory() { + return null; + } + + /** + * Returns an instance of {@link NameBasedActionResolver} unless a subclass has overridden the + * default. + * + * @return ActionResolver an instance of the configured resolver + */ + public ActionResolver getActionResolver() { + return this.actionResolver; + } + + /** Allows subclasses to initialize a non-default ActionResovler. */ + protected ActionResolver initActionResolver() { + return null; + } + + /** + * Returns an instance of {@link DefaultActionBeanPropertyBinder} unless a subclass has overridden + * the default. + * + * @return ActionBeanPropertyBinder an instance of the configured binder + */ + public ActionBeanPropertyBinder getActionBeanPropertyBinder() { + return this.actionBeanPropertyBinder; + } + + /** Allows subclasses to initialize a non-default ActionBeanPropertyBinder. */ + protected ActionBeanPropertyBinder initActionBeanPropertyBinder() { + return null; + } + + /** + * Returns the configured ActionBeanContextFactory. Unless a subclass has configured a custom one, + * the instance will be a DefaultActionBeanContextFactory. + * + * @return ActionBeanContextFactory an instance of a factory for creating ActionBeanContexts + */ + public ActionBeanContextFactory getActionBeanContextFactory() { + return this.actionBeanContextFactory; + } + + /** Allows subclasses to initialize a non-default ActionBeanContextFactory. */ + protected ActionBeanContextFactory initActionBeanContextFactory() { + return null; + } + + /** + * Returns an instance of {@link DefaultTypeConverterFactory} unless a subclass has overridden the + * default.. + * + * @return TypeConverterFactory an instance of the configured factory. + */ + public TypeConverterFactory getTypeConverterFactory() { + return this.typeConverterFactory; + } + + /** Allows subclasses to initialize a non-default TypeConverterFactory. */ + protected TypeConverterFactory initTypeConverterFactory() { + return null; + } + + /** + * Returns an instance of a LocalizationBundleFactory. By default this will be an instance of + * DefaultLocalizationBundleFactory unless another type has been configured. + */ + public LocalizationBundleFactory getLocalizationBundleFactory() { + return this.localizationBundleFactory; + } + + /** Allows subclasses to initialize a non-default LocalizationBundleFactory. */ + protected LocalizationBundleFactory initLocalizationBundleFactory() { + return null; + } + + /** + * Returns an instance of a LocalePicker. Unless a subclass has picked another implementation will + * return an instance of DefaultLocalePicker. + */ + public LocalePicker getLocalePicker() { + return this.localePicker; + } + + /** Allows subclasses to initialize a non-default LocalePicker. */ + protected LocalePicker initLocalePicker() { + return null; + } + + /** + * Returns an instance of a FormatterFactory. Unless a subclass has picked another implementation + * will return an instance of DefaultFormatterFactory. + */ + public FormatterFactory getFormatterFactory() { + return this.formatterFactory; + } + + /** Allows subclasses to initialize a non-default FormatterFactory. */ + protected FormatterFactory initFormatterFactory() { + return null; + } + + /** + * Returns an instance of a TagErrorRendererFactory. Unless a subclass has picked another + * implementation, will return an instance of DefaultTagErrorRendererFactory. + */ + public TagErrorRendererFactory getTagErrorRendererFactory() { + return tagErrorRendererFactory; + } + + /** Allows subclasses to initialize a non-default TagErrorRendererFactory instance to be used. */ + protected TagErrorRendererFactory initTagErrorRendererFactory() { + return null; + } + + /** + * Returns an instance of a PopulationsStrategy. Unless a subclass has picked another + * implementation, will return an instance of {@link + * net.sourceforge.stripes.tag.BeanFirstPopulationStrategy}. + * + * @since Stripes 1.6 + */ + public PopulationStrategy getPopulationStrategy() { + return this.populationStrategy; + } + + /** Allows subclasses to initialize a non-default PopulationStrategy instance to be used. */ + protected PopulationStrategy initPopulationStrategy() { + return null; + } + + /** + * Returns an instance of an ExceptionHandler. Unless a subclass has picked another + * implementation, will return an instance of {@link + * net.sourceforge.stripes.exception.DefaultExceptionHandler}. + */ + public ExceptionHandler getExceptionHandler() { + return this.exceptionHandler; + } + + /** Allows subclasses to initialize a non-default ExceptionHandler instance to be used. */ + protected ExceptionHandler initExceptionHandler() { + return null; + } + + /** + * Returns an instance of MultipartWrapperFactory that can be used by Stripes to construct + * MultipartWrapper instances for dealing with multipart requests (those containing file uploads). + * + * @return MultipartWrapperFactory an instance of the wrapper factory + */ + public MultipartWrapperFactory getMultipartWrapperFactory() { + return this.multipartWrapperFactory; + } + + /** Allows subclasses to initialize a non-default MultipartWrapperFactory. */ + protected MultipartWrapperFactory initMultipartWrapperFactory() { + return null; + } + + /** + * Returns an instance of {@link ValidationMetadataProvider} that can be used by Stripes to + * determine what validations need to be applied during {@link + * LifecycleStage#BindingAndValidation}. + * + * @return an instance of {@link ValidationMetadataProvider} + */ + public ValidationMetadataProvider getValidationMetadataProvider() { + return this.validationMetadataProvider; + } + + /** Allows subclasses to initialize a non-default {@link ValidationMetadataProvider}. */ + protected ValidationMetadataProvider initValidationMetadataProvider() { + return null; + } + + /** + * Returns a list of interceptors that should be executed around the lifecycle stage indicated. By + * default returns a single element list containing the {@link BeforeAfterMethodInterceptor}. + */ + public Collection getInterceptors(LifecycleStage stage) { + Collection interceptors = this.interceptors.get(stage); + if (interceptors == null) { + interceptors = Collections.emptyList(); } - - /** Allows subclasses to initialize a non-default TagErrorRendererFactory instance to be used. */ - protected TagErrorRendererFactory initTagErrorRendererFactory() { return null; } - - /** - * Returns an instance of a PopulationsStrategy. Unless a subclass has picked another - * implementation, will return an instance of - * {@link net.sourceforge.stripes.tag.BeanFirstPopulationStrategy}. - * @since Stripes 1.6 - */ - public PopulationStrategy getPopulationStrategy() { return this.populationStrategy; } - - /** Allows subclasses to initialize a non-default PopulationStrategy instance to be used. */ - protected PopulationStrategy initPopulationStrategy() { return null; } - - /** - * Returns an instance of an ExceptionHandler. Unless a subclass has picked another - * implementation, will return an instance of - * {@link net.sourceforge.stripes.exception.DefaultExceptionHandler}. - */ - public ExceptionHandler getExceptionHandler() { return this.exceptionHandler; } - - /** Allows subclasses to initialize a non-default ExceptionHandler instance to be used. */ - protected ExceptionHandler initExceptionHandler() { return null; } - - /** - * Returns an instance of MultipartWrapperFactory that can be used by Stripes to construct - * MultipartWrapper instances for dealing with multipart requests (those containing file - * uploads). - * - * @return MultipartWrapperFactory an instance of the wrapper factory - */ - public MultipartWrapperFactory getMultipartWrapperFactory() { - return this.multipartWrapperFactory; + return interceptors; + } + + /** + * Merges the two {@link Map}s of {@link LifecycleStage} to {@link Collection} of {@link + * Interceptor}. A simple {@link Map#putAll(Map)} does not work because it overwrites the + * collections in the map instead of adding to them. + */ + protected void mergeInterceptorMaps( + Map > dst, + Map > src) { + for (Map.Entry > entry : src.entrySet()) { + Collection collection = dst.get(entry.getKey()); + if (collection == null) { + collection = new LinkedList (); + dst.put(entry.getKey(), collection); + } + collection.addAll(entry.getValue()); } - - - /** Allows subclasses to initialize a non-default MultipartWrapperFactory. */ - protected MultipartWrapperFactory initMultipartWrapperFactory() { return null; } - - /** - * Returns an instance of {@link ValidationMetadataProvider} that can be used by Stripes to - * determine what validations need to be applied during - * {@link LifecycleStage#BindingAndValidation}. - * - * @return an instance of {@link ValidationMetadataProvider} - */ - public ValidationMetadataProvider getValidationMetadataProvider() { - return this.validationMetadataProvider; + } + + /** + * Adds the interceptor to the map, associating it with the {@link LifecycleStage}s indicated by + * the {@link Intercepts} annotation. If the interceptor implements {@link ConfigurableComponent}, + * then its init() method will be called. + */ + protected void addInterceptor( + Map > map, Interceptor interceptor) { + Class extends Interceptor> type = interceptor.getClass(); + Intercepts intercepts = type.getAnnotation(Intercepts.class); + if (intercepts == null) { + log.error( + "An interceptor of type ", + type.getName(), + " was configured ", + "but was not marked with an @Intercepts annotation. As a ", + "result it is not possible to determine at which ", + "lifecycle stages the interceptor should be applied. This ", + "interceptor will be ignored."); + return; + } else { + log.debug( + "Configuring interceptor '", + type.getSimpleName(), + "', for lifecycle stages: ", + intercepts.value()); } - /** Allows subclasses to initialize a non-default {@link ValidationMetadataProvider}. */ - protected ValidationMetadataProvider initValidationMetadataProvider() { return null; } - - /** - * Returns a list of interceptors that should be executed around the lifecycle stage - * indicated. By default returns a single element list containing the - * {@link BeforeAfterMethodInterceptor}. - */ - public Collection getInterceptors(LifecycleStage stage) { - Collection interceptors = this.interceptors.get(stage); - if (interceptors == null) { - interceptors = Collections.emptyList(); - } - return interceptors; + // call init() if the interceptor implements ConfigurableComponent + if (interceptor instanceof ConfigurableComponent) { + try { + ((ConfigurableComponent) interceptor).init(this); + } catch (Exception e) { + log.error("Error initializing interceptor of type " + type.getName(), e); + } } - - /** - * Merges the two {@link Map}s of {@link LifecycleStage} to {@link Collection} of - * {@link Interceptor}. A simple {@link Map#putAll(Map)} does not work because it overwrites - * the collections in the map instead of adding to them. - */ - protected void mergeInterceptorMaps(Map > dst, - Map > src) { - for (Map.Entry > entry : src.entrySet()) { - Collection collection = dst.get(entry.getKey()); - if (collection == null) { - collection = new LinkedList (); - dst.put(entry.getKey(), collection); - } - collection.addAll(entry.getValue()); - } - } - - /** - * Adds the interceptor to the map, associating it with the {@link LifecycleStage}s indicated - * by the {@link Intercepts} annotation. If the interceptor implements - * {@link ConfigurableComponent}, then its init() method will be called. - */ - protected void addInterceptor(Map > map, - Interceptor interceptor) { - Class extends Interceptor> type = interceptor.getClass(); - Intercepts intercepts = type.getAnnotation(Intercepts.class); - if (intercepts == null) { - log.error("An interceptor of type ", type.getName(), " was configured ", - "but was not marked with an @Intercepts annotation. As a ", - "result it is not possible to determine at which ", - "lifecycle stages the interceptor should be applied. This ", - "interceptor will be ignored."); - return; - } - else { - log.debug("Configuring interceptor '", type.getSimpleName(), - "', for lifecycle stages: ", intercepts.value()); - } - - // call init() if the interceptor implements ConfigurableComponent - if (interceptor instanceof ConfigurableComponent) { - try { - ((ConfigurableComponent) interceptor).init(this); - } - catch (Exception e) { - log.error("Error initializing interceptor of type " + type.getName(), e); - } - } - for (LifecycleStage stage : intercepts.value()) { - Collection stack = map.get(stage); - if (stack == null) { - stack = new LinkedList (); - map.put(stage, stack); - } + for (LifecycleStage stage : intercepts.value()) { + Collection stack = map.get(stage); + if (stack == null) { + stack = new LinkedList (); + map.put(stage, stack); + } - stack.add(interceptor); - } + stack.add(interceptor); } - - /** Instantiates the core interceptors, allowing subclasses to override the default behavior */ - protected Map > initCoreInterceptors() { - Map > interceptors = new HashMap >(); - addInterceptor(interceptors, new BeforeAfterMethodInterceptor()); - addInterceptor(interceptors, new HttpCacheInterceptor()); - return interceptors; - } - - /** Allows subclasses to initialize a non-default Map of Interceptor instances. */ - protected Map > initInterceptors() { return null; } + } + + /** Instantiates the core interceptors, allowing subclasses to override the default behavior */ + protected Map > initCoreInterceptors() { + Map > interceptors = + new HashMap >(); + addInterceptor(interceptors, new BeforeAfterMethodInterceptor()); + addInterceptor(interceptors, new HttpCacheInterceptor()); + return interceptors; + } + + /** Allows subclasses to initialize a non-default Map of Interceptor instances. */ + protected Map > initInterceptors() { + return null; + } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/config/DontAutoLoad.java b/stripes/src/main/java/net/sourceforge/stripes/config/DontAutoLoad.java index bd31c3046..a6418cc7b 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/config/DontAutoLoad.java +++ b/stripes/src/main/java/net/sourceforge/stripes/config/DontAutoLoad.java @@ -19,24 +19,22 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - import net.sourceforge.stripes.exception.AutoExceptionHandler; import net.sourceforge.stripes.format.Formatter; import net.sourceforge.stripes.validation.TypeConverter; import net.sourceforge.stripes.validation.Validate; /** - * When applied to a Stripes extension class (e.g., one that implements {@link Formatter}, - * {@link TypeConverter}, {@link AutoExceptionHandler}, etc.), this annotation indicates that the - * class should not be loaded via autodiscovery. This is useful, for example, when you - * have a {@link TypeConverter} that is applied in special cases via {@link Validate#converter()} - * but should not be used for all the type conversions to which it applies. - * + * When applied to a Stripes extension class (e.g., one that implements {@link Formatter}, {@link + * TypeConverter}, {@link AutoExceptionHandler}, etc.), this annotation indicates that the class + * should not be loaded via autodiscovery. This is useful, for example, when you have a + * {@link TypeConverter} that is applied in special cases via {@link Validate#converter()} but + * should not be used for all the type conversions to which it applies. + * * @author Ben Gunter * @since Stripes 1.5 */ @Retention(RetentionPolicy.RUNTIME) -@Target( { ElementType.TYPE }) +@Target({ElementType.TYPE}) @Documented -public @interface DontAutoLoad { -} +public @interface DontAutoLoad {} diff --git a/stripes/src/main/java/net/sourceforge/stripes/config/RuntimeConfiguration.java b/stripes/src/main/java/net/sourceforge/stripes/config/RuntimeConfiguration.java index 24708c040..ac04adb91 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/config/RuntimeConfiguration.java +++ b/stripes/src/main/java/net/sourceforge/stripes/config/RuntimeConfiguration.java @@ -19,7 +19,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - import net.sourceforge.stripes.controller.ActionBeanContextFactory; import net.sourceforge.stripes.controller.ActionBeanPropertyBinder; import net.sourceforge.stripes.controller.ActionResolver; @@ -42,300 +41,338 @@ import net.sourceforge.stripes.validation.ValidationMetadataProvider; /** - * Configuration class that uses the BootstrapPropertyResolver to look for configuration values, - * and when it cannot find a value, falls back on the DefaultConfiguration to supply default - * values. In general, the RuntimeConfiguration will operate in the following pattern:
+ * Configuration class that uses the BootstrapPropertyResolver to look for configuration values, and + * when it cannot find a value, falls back on the DefaultConfiguration to supply default values. In + * general, the RuntimeConfiguration will operate in the following pattern: * *- *
* * @author Tim Fennell */ public class RuntimeConfiguration extends DefaultConfiguration { - /** Log implementation for use within this class. */ - private static final Log log = Log.getInstance(RuntimeConfiguration.class); + /** Log implementation for use within this class. */ + private static final Log log = Log.getInstance(RuntimeConfiguration.class); - /** The Configuration Key for enabling debug mode. */ - public static final String DEBUG_MODE = "Stripes.DebugMode"; + /** The Configuration Key for enabling debug mode. */ + public static final String DEBUG_MODE = "Stripes.DebugMode"; - /** The Configuration Key for looking up the name of the ObjectFactory class */ - public static final String OBJECT_FACTORY = "ObjectFactory.Class"; + /** The Configuration Key for looking up the name of the ObjectFactory class */ + public static final String OBJECT_FACTORY = "ObjectFactory.Class"; - /** The Configuration Key for looking up the name of the ActionResolver class. */ - public static final String ACTION_RESOLVER = "ActionResolver.Class"; + /** The Configuration Key for looking up the name of the ActionResolver class. */ + public static final String ACTION_RESOLVER = "ActionResolver.Class"; - /** The Configuration Key for looking up the name of the ActionResolver class. */ - public static final String ACTION_BEAN_PROPERTY_BINDER = "ActionBeanPropertyBinder.Class"; + /** The Configuration Key for looking up the name of the ActionResolver class. */ + public static final String ACTION_BEAN_PROPERTY_BINDER = "ActionBeanPropertyBinder.Class"; - /** The Configuration Key for looking up the name of an ActionBeanContextFactory class. */ - public static final String ACTION_BEAN_CONTEXT_FACTORY = "ActionBeanContextFactory.Class"; + /** The Configuration Key for looking up the name of an ActionBeanContextFactory class. */ + public static final String ACTION_BEAN_CONTEXT_FACTORY = "ActionBeanContextFactory.Class"; - /** The Configuration Key for looking up the name of the TypeConverterFactory class. */ - public static final String TYPE_CONVERTER_FACTORY = "TypeConverterFactory.Class"; + /** The Configuration Key for looking up the name of the TypeConverterFactory class. */ + public static final String TYPE_CONVERTER_FACTORY = "TypeConverterFactory.Class"; - /** The Configuration Key for looking up the name of the LocalizationBundleFactory class. */ - public static final String LOCALIZATION_BUNDLE_FACTORY = "LocalizationBundleFactory.Class"; + /** The Configuration Key for looking up the name of the LocalizationBundleFactory class. */ + public static final String LOCALIZATION_BUNDLE_FACTORY = "LocalizationBundleFactory.Class"; - /** The Configuration Key for looking up the name of the LocalizationBundleFactory class. */ - public static final String LOCALE_PICKER = "LocalePicker.Class"; + /** The Configuration Key for looking up the name of the LocalizationBundleFactory class. */ + public static final String LOCALE_PICKER = "LocalePicker.Class"; - /** The Configuration Key for looking up the name of the FormatterFactory class. */ - public static final String FORMATTER_FACTORY = "FormatterFactory.Class"; + /** The Configuration Key for looking up the name of the FormatterFactory class. */ + public static final String FORMATTER_FACTORY = "FormatterFactory.Class"; - /** The Configuration Key for looking up the name of the TagErrorRendererFactory class */ - public static final String TAG_ERROR_RENDERER_FACTORY = "TagErrorRendererFactory.Class"; + /** The Configuration Key for looking up the name of the TagErrorRendererFactory class */ + public static final String TAG_ERROR_RENDERER_FACTORY = "TagErrorRendererFactory.Class"; - /** The Configuration Key for looking up the name of the PopulationStrategy class */ - public static final String POPULATION_STRATEGY = "PopulationStrategy.Class"; + /** The Configuration Key for looking up the name of the PopulationStrategy class */ + public static final String POPULATION_STRATEGY = "PopulationStrategy.Class"; - /** The Configuration Key for looking up the name of the ExceptionHandler class */ - public static final String EXCEPTION_HANDLER = "ExceptionHandler.Class"; + /** The Configuration Key for looking up the name of the ExceptionHandler class */ + public static final String EXCEPTION_HANDLER = "ExceptionHandler.Class"; - /** The Configuration Key for looking up the name of the MultipartWrapperFactory class */ - public static final String MULTIPART_WRAPPER_FACTORY = "MultipartWrapperFactory.Class"; + /** The Configuration Key for looking up the name of the MultipartWrapperFactory class */ + public static final String MULTIPART_WRAPPER_FACTORY = "MultipartWrapperFactory.Class"; - /** The Configuration Key for looking up the name of the ValidationMetadataProvider class */ - public static final String VALIDATION_METADATA_PROVIDER = "ValidationMetadataProvider.Class"; + /** The Configuration Key for looking up the name of the ValidationMetadataProvider class */ + public static final String VALIDATION_METADATA_PROVIDER = "ValidationMetadataProvider.Class"; - /** The Configuration Key for looking up the comma separated list of core interceptor classes. */ - public static final String CORE_INTERCEPTOR_LIST = "CoreInterceptor.Classes"; + /** The Configuration Key for looking up the comma separated list of core interceptor classes. */ + public static final String CORE_INTERCEPTOR_LIST = "CoreInterceptor.Classes"; - /** The Configuration Key for looking up the comma separated list of interceptor classes. */ - public static final String INTERCEPTOR_LIST = "Interceptor.Classes"; - - /** Looks for a true/false value in config. */ - @Override protected Boolean initDebugMode() { - try { - return Boolean.valueOf(getBootstrapPropertyResolver().getProperty(DEBUG_MODE) - .toLowerCase()); - } - catch (Exception e) { - return null; - } - } + /** The Configuration Key for looking up the comma separated list of interceptor classes. */ + public static final String INTERCEPTOR_LIST = "Interceptor.Classes"; - /** Looks for a class name in config and uses that to create the component. */ - @Override protected ObjectFactory initObjectFactory() { - return initializeComponent(ObjectFactory.class, OBJECT_FACTORY); + /** Looks for a true/false value in config. */ + @Override + protected Boolean initDebugMode() { + try { + return Boolean.valueOf(getBootstrapPropertyResolver().getProperty(DEBUG_MODE).toLowerCase()); + } catch (Exception e) { + return null; } - - /** Looks for a class name in config and uses that to create the component. */ - @Override protected ActionResolver initActionResolver() { - return initializeComponent(ActionResolver.class, ACTION_RESOLVER); - } - - /** Looks for a class name in config and uses that to create the component. */ - @Override protected ActionBeanPropertyBinder initActionBeanPropertyBinder() { - return initializeComponent(ActionBeanPropertyBinder.class, ACTION_BEAN_PROPERTY_BINDER); - } - - /** Looks for a class name in config and uses that to create the component. */ - @Override protected ActionBeanContextFactory initActionBeanContextFactory() { - return initializeComponent(ActionBeanContextFactory.class, ACTION_BEAN_CONTEXT_FACTORY); - } - - /** Looks for a class name in config and uses that to create the component. */ - @Override protected TypeConverterFactory initTypeConverterFactory() { - return initializeComponent(TypeConverterFactory.class, TYPE_CONVERTER_FACTORY); - } - - /** Looks for a class name in config and uses that to create the component. */ - @Override protected LocalizationBundleFactory initLocalizationBundleFactory() { - return initializeComponent(LocalizationBundleFactory.class, LOCALIZATION_BUNDLE_FACTORY); - } - - /** Looks for a class name in config and uses that to create the component. */ - @Override protected LocalePicker initLocalePicker() { - return initializeComponent(LocalePicker.class, LOCALE_PICKER); - } - - /** Looks for a class name in config and uses that to create the component. */ - @Override protected FormatterFactory initFormatterFactory() { - return initializeComponent(FormatterFactory.class, FORMATTER_FACTORY); + } + + /** Looks for a class name in config and uses that to create the component. */ + @Override + protected ObjectFactory initObjectFactory() { + return initializeComponent(ObjectFactory.class, OBJECT_FACTORY); + } + + /** Looks for a class name in config and uses that to create the component. */ + @Override + protected ActionResolver initActionResolver() { + return initializeComponent(ActionResolver.class, ACTION_RESOLVER); + } + + /** Looks for a class name in config and uses that to create the component. */ + @Override + protected ActionBeanPropertyBinder initActionBeanPropertyBinder() { + return initializeComponent(ActionBeanPropertyBinder.class, ACTION_BEAN_PROPERTY_BINDER); + } + + /** Looks for a class name in config and uses that to create the component. */ + @Override + protected ActionBeanContextFactory initActionBeanContextFactory() { + return initializeComponent(ActionBeanContextFactory.class, ACTION_BEAN_CONTEXT_FACTORY); + } + + /** Looks for a class name in config and uses that to create the component. */ + @Override + protected TypeConverterFactory initTypeConverterFactory() { + return initializeComponent(TypeConverterFactory.class, TYPE_CONVERTER_FACTORY); + } + + /** Looks for a class name in config and uses that to create the component. */ + @Override + protected LocalizationBundleFactory initLocalizationBundleFactory() { + return initializeComponent(LocalizationBundleFactory.class, LOCALIZATION_BUNDLE_FACTORY); + } + + /** Looks for a class name in config and uses that to create the component. */ + @Override + protected LocalePicker initLocalePicker() { + return initializeComponent(LocalePicker.class, LOCALE_PICKER); + } + + /** Looks for a class name in config and uses that to create the component. */ + @Override + protected FormatterFactory initFormatterFactory() { + return initializeComponent(FormatterFactory.class, FORMATTER_FACTORY); + } + + /** Looks for a class name in config and uses that to create the component. */ + @Override + protected TagErrorRendererFactory initTagErrorRendererFactory() { + return initializeComponent(TagErrorRendererFactory.class, TAG_ERROR_RENDERER_FACTORY); + } + + /** Looks for a class name in config and uses that to create the component. */ + @Override + protected PopulationStrategy initPopulationStrategy() { + return initializeComponent(PopulationStrategy.class, POPULATION_STRATEGY); + } + + /** Looks for a class name in config and uses that to create the component. */ + @Override + protected ExceptionHandler initExceptionHandler() { + return initializeComponent(ExceptionHandler.class, EXCEPTION_HANDLER); + } + + /** Looks for a class name in config and uses that to create the component. */ + @Override + protected MultipartWrapperFactory initMultipartWrapperFactory() { + return initializeComponent(MultipartWrapperFactory.class, MULTIPART_WRAPPER_FACTORY); + } + + /** Looks for a class name in config and uses that to create the component. */ + @Override + protected ValidationMetadataProvider initValidationMetadataProvider() { + return initializeComponent(ValidationMetadataProvider.class, VALIDATION_METADATA_PROVIDER); + } + + /** + * Looks for a list of class names separated by commas under the configuration key {@link + * #CORE_INTERCEPTOR_LIST}. White space surrounding the class names is trimmed, the classes + * instantiated and then stored under the lifecycle stage(s) they should intercept. + * + * @return a Map of {@link LifecycleStage} to Collection of {@link Interceptor} + */ + @Override + protected Map- Look for the value of a configuration property in the BootstrapProperties
- *- If the value exists, the configuration will attempt to use it (usually to instantiate - * a class). If an exception occurs, the RuntimeConfiguration will throw an exception and - * not provide a value. In most cases this will be fatal!
- *- If the value does not exist, the default from DefaultConfiguration will be used.
+ *- Look for the value of a configuration property in the BootstrapProperties + *
- If the value exists, the configuration will attempt to use it (usually to instantiate a + * class). If an exception occurs, the RuntimeConfiguration will throw an exception and not + * provide a value. In most cases this will be fatal! + *
- If the value does not exist, the default from DefaultConfiguration will be used. *
> initCoreInterceptors() { + List > coreInterceptorClasses = + getBootstrapPropertyResolver().getClassPropertyList(CORE_INTERCEPTOR_LIST); + if (coreInterceptorClasses.size() == 0) return super.initCoreInterceptors(); + else return initInterceptors(coreInterceptorClasses); + } + + /** + * Looks for a list of class names separated by commas under the configuration key {@link + * #INTERCEPTOR_LIST}. White space surrounding the class names is trimmed, the classes + * instantiated and then stored under the lifecycle stage(s) they should intercept. + * + * @return a Map of {@link LifecycleStage} to Collection of {@link Interceptor} + */ + @Override + protected Map > initInterceptors() { + return initInterceptors( + getBootstrapPropertyResolver().getClassPropertyList(INTERCEPTOR_LIST, Interceptor.class)); + } + + /** + * Splits a comma-separated list of class names and maps each {@link LifecycleStage} to the + * interceptors in the list that intercept it. Also automatically finds Interceptors in packages + * listed in {@link BootstrapPropertyResolver#PACKAGES} if searchExtensionPackages is true. + * + * @return a Map of {@link LifecycleStage} to Collection of {@link Interceptor} + */ + @SuppressWarnings("unchecked") + protected Map > initInterceptors(List classes) { + + Map > map = + new HashMap >(); + + for (Object type : classes) { + try { + Interceptor interceptor = + getObjectFactory().newInstance((Class extends Interceptor>) type); + addInterceptor(map, interceptor); + } catch (Exception e) { + throw new StripesRuntimeException( + "Could not instantiate configured Interceptor [" + type.getClass().getName() + "].", e); + } } - /** Looks for a class name in config and uses that to create the component. */ - @Override protected TagErrorRendererFactory initTagErrorRendererFactory() { - return initializeComponent(TagErrorRendererFactory.class, TAG_ERROR_RENDERER_FACTORY); - } - - /** Looks for a class name in config and uses that to create the component. */ - @Override protected PopulationStrategy initPopulationStrategy() { - return initializeComponent(PopulationStrategy.class, POPULATION_STRATEGY); - } - - /** Looks for a class name in config and uses that to create the component. */ - @Override protected ExceptionHandler initExceptionHandler() { - return initializeComponent(ExceptionHandler.class, EXCEPTION_HANDLER); - } - - /** Looks for a class name in config and uses that to create the component. */ - @Override protected MultipartWrapperFactory initMultipartWrapperFactory() { - return initializeComponent(MultipartWrapperFactory.class, MULTIPART_WRAPPER_FACTORY); - } - - /** Looks for a class name in config and uses that to create the component. */ - @Override protected ValidationMetadataProvider initValidationMetadataProvider() { - return initializeComponent(ValidationMetadataProvider.class, VALIDATION_METADATA_PROVIDER); - } - - /** - * Looks for a list of class names separated by commas under the configuration key - * {@link #CORE_INTERCEPTOR_LIST}. White space surrounding the class names is trimmed, - * the classes instantiated and then stored under the lifecycle stage(s) they should - * intercept. - * - * @return a Map of {@link LifecycleStage} to Collection of {@link Interceptor} - */ - @Override - protected Map > initCoreInterceptors() { - List > coreInterceptorClasses = getBootstrapPropertyResolver().getClassPropertyList(CORE_INTERCEPTOR_LIST); - if (coreInterceptorClasses.size() == 0) - return super.initCoreInterceptors(); - else - return initInterceptors(coreInterceptorClasses); - } - - /** - * Looks for a list of class names separated by commas under the configuration key - * {@link #INTERCEPTOR_LIST}. White space surrounding the class names is trimmed, - * the classes instantiated and then stored under the lifecycle stage(s) they should - * intercept. - * - * @return a Map of {@link LifecycleStage} to Collection of {@link Interceptor} - */ - @Override - protected Map > initInterceptors() { - return initInterceptors(getBootstrapPropertyResolver().getClassPropertyList(INTERCEPTOR_LIST, Interceptor.class)); - } - - /** - * Splits a comma-separated list of class names and maps each {@link LifecycleStage} to the - * interceptors in the list that intercept it. Also automatically finds Interceptors in - * packages listed in {@link BootstrapPropertyResolver#PACKAGES} if searchExtensionPackages is true. - * - * @return a Map of {@link LifecycleStage} to Collection of {@link Interceptor} - */ - @SuppressWarnings("unchecked") - protected Map > initInterceptors(List classes) { - - Map > map = new HashMap >(); - - for (Object type : classes) { - try { - Interceptor interceptor = getObjectFactory().newInstance( - (Class extends Interceptor>) type); - addInterceptor(map, interceptor); - } - catch (Exception e) { - throw new StripesRuntimeException("Could not instantiate configured Interceptor [" - + type.getClass().getName() + "].", e); - } + return map; + } + + /** + * Internal utility method that is used to implement the main pattern of this class: lookup the + * name of a class based on a property name, instantiate the named class and initialize it. + * + * @param componentType a Class object representing a subclass of ConfigurableComponent + * @param propertyName the name of the property to look up for the class name + * @return an instance of the component, or null if one was not configured. + */ + @SuppressWarnings("unchecked") + protected T initializeComponent( + Class componentType, String propertyName) { + Class clazz = getBootstrapPropertyResolver().getClassProperty(propertyName, componentType); + if (clazz != null) { + try { + T component; + + ObjectFactory objectFactory = getObjectFactory(); + if (objectFactory != null) { + component = objectFactory.newInstance((Class ) clazz); + } else { + component = (T) clazz.newInstance(); } - return map; + component.init(this); + return component; + } catch (Exception e) { + throw new StripesRuntimeException( + "Could not instantiate configured " + + componentType.getSimpleName() + + " of type [" + + clazz.getSimpleName() + + "]. Please check " + + "the configuration parameters specified in your web.xml.", + e); + } + } else { + return null; } - - /** - * Internal utility method that is used to implement the main pattern of this class: lookup the - * name of a class based on a property name, instantiate the named class and initialize it. - * - * @param componentType a Class object representing a subclass of ConfigurableComponent - * @param propertyName the name of the property to look up for the class name - * @return an instance of the component, or null if one was not configured. - */ - @SuppressWarnings("unchecked") - protected T initializeComponent(Class componentType, - String propertyName) { - Class clazz = getBootstrapPropertyResolver().getClassProperty(propertyName, componentType); - if (clazz != null) { - try { - T component; - - ObjectFactory objectFactory = getObjectFactory(); - if (objectFactory != null) { - component = objectFactory.newInstance((Class ) clazz); - } - else { - component = (T) clazz.newInstance(); - } - - component.init(this); - return component; - } - catch (Exception e) { - throw new StripesRuntimeException("Could not instantiate configured " - + componentType.getSimpleName() + " of type [" + clazz.getSimpleName() - + "]. Please check " - + "the configuration parameters specified in your web.xml.", e); - - } + } + + /** + * Calls super.init() then adds Formatters and TypeConverters found in packages listed in {@link + * BootstrapPropertyResolver#PACKAGES} to their respective factories. + */ + @SuppressWarnings("unchecked") + @Override + public void init() { + super.init(); + + List > formatters = + getBootstrapPropertyResolver().getClassPropertyList(Formatter.class); + for (Class extends Formatter> formatter : formatters) { + Type[] typeArguments = ReflectUtil.getActualTypeArguments(formatter, Formatter.class); + log.trace("Found Formatter [", formatter, "] - type parameters: ", typeArguments); + if ((typeArguments != null) + && (typeArguments.length == 1) + && !typeArguments[0].equals(Object.class)) { + if (typeArguments[0] instanceof Class) { + log.debug( + "Adding auto-discovered Formatter [", + formatter, + "] for [", + typeArguments[0], + "] (from type parameter)"); + getFormatterFactory() + .add((Class>) typeArguments[0], (Class extends Formatter>>) formatter); + } else { + log.warn("Type parameter for non-abstract Formatter [", formatter, "] is not a class."); } - else { - return null; + } + + TargetTypes targetTypes = formatter.getAnnotation(TargetTypes.class); + if (targetTypes != null) { + for (Class> targetType : targetTypes.value()) { + log.debug( + "Adding auto-discovered Formatter [", + formatter, + "] for [", + targetType, + "] (from TargetTypes annotation)"); + getFormatterFactory().add(targetType, (Class extends Formatter>>) formatter); } + } } - /** - * Calls super.init() then adds Formatters and TypeConverters found in - * packages listed in {@link BootstrapPropertyResolver#PACKAGES} to their respective factories. - */ - @SuppressWarnings("unchecked") - @Override - public void init() { - super.init(); - - List > formatters = getBootstrapPropertyResolver().getClassPropertyList(Formatter.class); - for (Class extends Formatter> formatter : formatters) { - Type[] typeArguments = ReflectUtil.getActualTypeArguments(formatter, Formatter.class); - log.trace("Found Formatter [", formatter, "] - type parameters: ", typeArguments); - if ((typeArguments != null) && (typeArguments.length == 1) - && !typeArguments[0].equals(Object.class)) { - if (typeArguments[0] instanceof Class) { - log.debug("Adding auto-discovered Formatter [", formatter, "] for [", typeArguments[0], "] (from type parameter)"); - getFormatterFactory().add((Class>) typeArguments[0], (Class extends Formatter>>) formatter); - } - else { - log.warn("Type parameter for non-abstract Formatter [", formatter, "] is not a class."); - } - } - - TargetTypes targetTypes = formatter.getAnnotation(TargetTypes.class); - if (targetTypes != null) { - for (Class> targetType : targetTypes.value()) { - log.debug("Adding auto-discovered Formatter [", formatter, "] for [", targetType, "] (from TargetTypes annotation)"); - getFormatterFactory().add(targetType, (Class extends Formatter>>) formatter); - } - } + List > typeConverters = + getBootstrapPropertyResolver().getClassPropertyList(TypeConverter.class); + for (Class extends TypeConverter> typeConverter : typeConverters) { + Type[] typeArguments = ReflectUtil.getActualTypeArguments(typeConverter, TypeConverter.class); + log.trace("Found TypeConverter [", typeConverter, "] - type parameters: ", typeArguments); + if ((typeArguments != null) + && (typeArguments.length == 1) + && !typeArguments[0].equals(Object.class)) { + if (typeArguments[0] instanceof Class) { + log.debug( + "Adding auto-discovered TypeConverter [", + typeConverter, + "] for [", + typeArguments[0], + "] (from type parameter)"); + getTypeConverterFactory() + .add((Class>) typeArguments[0], (Class extends TypeConverter>>) typeConverter); + } else { + log.warn( + "Type parameter for non-abstract TypeConverter [", + typeConverter, + "] is not a class."); } - - List > typeConverters = getBootstrapPropertyResolver().getClassPropertyList(TypeConverter.class); - for (Class extends TypeConverter> typeConverter : typeConverters) { - Type[] typeArguments = ReflectUtil.getActualTypeArguments(typeConverter, TypeConverter.class); - log.trace("Found TypeConverter [", typeConverter, "] - type parameters: ", typeArguments); - if ((typeArguments != null) && (typeArguments.length == 1) - && !typeArguments[0].equals(Object.class)) { - if (typeArguments[0] instanceof Class) { - log.debug("Adding auto-discovered TypeConverter [", typeConverter, "] for [", typeArguments[0], "] (from type parameter)"); - getTypeConverterFactory().add((Class>) typeArguments[0], (Class extends TypeConverter>>) typeConverter); - } - else { - log.warn("Type parameter for non-abstract TypeConverter [", typeConverter, "] is not a class."); - } - } - - TargetTypes targetTypes = typeConverter.getAnnotation(TargetTypes.class); - if (targetTypes != null) { - for (Class> targetType : targetTypes.value()) { - log.debug("Adding auto-discovered TypeConverter [", typeConverter, "] for [", targetType, "] (from TargetTypes annotation)"); - getTypeConverterFactory().add(targetType, (Class extends TypeConverter>>) typeConverter); - } - } + } + + TargetTypes targetTypes = typeConverter.getAnnotation(TargetTypes.class); + if (targetTypes != null) { + for (Class> targetType : targetTypes.value()) { + log.debug( + "Adding auto-discovered TypeConverter [", + typeConverter, + "] for [", + targetType, + "] (from TargetTypes annotation)"); + getTypeConverterFactory() + .add(targetType, (Class extends TypeConverter>>) typeConverter); } + } } + } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/config/TargetTypes.java b/stripes/src/main/java/net/sourceforge/stripes/config/TargetTypes.java index 71c95eb30..818739cef 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/config/TargetTypes.java +++ b/stripes/src/main/java/net/sourceforge/stripes/config/TargetTypes.java @@ -21,15 +21,14 @@ import java.lang.annotation.Target; /** - * Annotation used to indicate classes, interfaces, and annotations that - * a Formatter or TypeConverter can handle. - * - * @author Aaron Porter + * Annotation used to indicate classes, interfaces, and annotations that a Formatter or + * TypeConverter can handle. * + * @author Aaron Porter */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented public @interface TargetTypes { - Class>[] value(); -} \ No newline at end of file + Class>[] value(); +} diff --git a/stripes/src/main/java/net/sourceforge/stripes/controller/ActionBeanContextFactory.java b/stripes/src/main/java/net/sourceforge/stripes/controller/ActionBeanContextFactory.java index 906483580..c69c0015a 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/controller/ActionBeanContextFactory.java +++ b/stripes/src/main/java/net/sourceforge/stripes/controller/ActionBeanContextFactory.java @@ -14,30 +14,28 @@ */ package net.sourceforge.stripes.controller; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import net.sourceforge.stripes.action.ActionBeanContext; import net.sourceforge.stripes.config.ConfigurableComponent; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletRequest; - /** - * Interface for classes that can instantiate and supply new instances of the - * ActionBeanContext class, or subclasses thereof. + * Interface for classes that can instantiate and supply new instances of the ActionBeanContext + * class, or subclasses thereof. * * @author Tim Fennell */ public interface ActionBeanContextFactory extends ConfigurableComponent { - - /** - * Creates and returns a new instance of ActionBeanContext or a subclass. - * - * @param request the current HttpServletRequest - * @param response the current HttpServletResponse - * @return a new instance of ActionBeanContext - * @throws ServletException if the ActionBeanContext class configured cannot be instantiated - */ - ActionBeanContext getContextInstance(HttpServletRequest request, - HttpServletResponse response) throws ServletException; -} \ No newline at end of file + /** + * Creates and returns a new instance of ActionBeanContext or a subclass. + * + * @param request the current HttpServletRequest + * @param response the current HttpServletResponse + * @return a new instance of ActionBeanContext + * @throws ServletException if the ActionBeanContext class configured cannot be instantiated + */ + ActionBeanContext getContextInstance(HttpServletRequest request, HttpServletResponse response) + throws ServletException; +} diff --git a/stripes/src/main/java/net/sourceforge/stripes/controller/ActionBeanPropertyBinder.java b/stripes/src/main/java/net/sourceforge/stripes/controller/ActionBeanPropertyBinder.java index e36817156..ea9970e19 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/controller/ActionBeanPropertyBinder.java +++ b/stripes/src/main/java/net/sourceforge/stripes/controller/ActionBeanPropertyBinder.java @@ -16,45 +16,45 @@ import net.sourceforge.stripes.action.ActionBean; import net.sourceforge.stripes.action.ActionBeanContext; -import net.sourceforge.stripes.validation.ValidationErrors; import net.sourceforge.stripes.config.ConfigurableComponent; +import net.sourceforge.stripes.validation.ValidationErrors; /** - * Interface for class(es) responsible for taking the String/String[] properties contained in the + * Interface for class(es) responsible for taking the String/String[] properties contained in the * HttpServletRequest and: + * *
- *
- *- Converting them to the rich type of the property on the target JavaBean
- *- Setting the properties on the JavaBean using the appropriate mechanism
+ *- Converting them to the rich type of the property on the target JavaBean + *
- Setting the properties on the JavaBean using the appropriate mechanism *
Implementations may also perform validations of the fields during binding. If validation + *
Implementations may also perform validations of the fields during binding. If validation * errors occur then the collection of ValidationErrors contained within the ActionBeanContext - * should be populated before returning.
+ * should be populated before returning. * * @author Tim Fennell */ public interface ActionBeanPropertyBinder extends ConfigurableComponent { - /** - * Populates all the properties in the request which have a matching property in the target - * bean. If additional properties exist in the request which are not present in the bean a - * message should be logged, but binding should continue without throwing any errors. - * - * @param bean the ActionBean to bind properties to - * @param context the ActionBeanContext containing the current request - * @param validate true indicates that validation should be run, false indicates that only - * type conversion should occur - */ - ValidationErrors bind(ActionBean bean, ActionBeanContext context, boolean validate); + /** + * Populates all the properties in the request which have a matching property in the target bean. + * If additional properties exist in the request which are not present in the bean a message + * should be logged, but binding should continue without throwing any errors. + * + * @param bean the ActionBean to bind properties to + * @param context the ActionBeanContext containing the current request + * @param validate true indicates that validation should be run, false indicates that only type + * conversion should occur + */ + ValidationErrors bind(ActionBean bean, ActionBeanContext context, boolean validate); - /** - * Bind an individual property with the name specified to the bean supplied. - * - * @param bean the ActionBean to bind the property to - * @param propertyName the name (including nested, indexed and mapped property names) of the - * property being bound - * @param propertyValue the value to be bound to the property on the bean - * @throws Exception thrown if the property cannot be bound for any reason - */ - void bind(ActionBean bean, String propertyName, Object propertyValue) throws Exception; + /** + * Bind an individual property with the name specified to the bean supplied. + * + * @param bean the ActionBean to bind the property to + * @param propertyName the name (including nested, indexed and mapped property names) of the + * property being bound + * @param propertyValue the value to be bound to the property on the bean + * @throws Exception thrown if the property cannot be bound for any reason + */ + void bind(ActionBean bean, String propertyName, Object propertyValue) throws Exception; } diff --git a/stripes/src/main/java/net/sourceforge/stripes/controller/ActionResolver.java b/stripes/src/main/java/net/sourceforge/stripes/controller/ActionResolver.java index a487ef304..32b975b8f 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/controller/ActionResolver.java +++ b/stripes/src/main/java/net/sourceforge/stripes/controller/ActionResolver.java @@ -14,158 +14,157 @@ */ package net.sourceforge.stripes.controller; +import java.lang.reflect.Method; +import java.util.Collection; import net.sourceforge.stripes.action.ActionBean; import net.sourceforge.stripes.action.ActionBeanContext; -import net.sourceforge.stripes.exception.StripesServletException; import net.sourceforge.stripes.config.ConfigurableComponent; - -import java.lang.reflect.Method; -import java.util.Collection; +import net.sourceforge.stripes.exception.StripesServletException; /** - *Resolvers are responsible for locating ActionBean instances that can handle the submitted - * request. Once an appropriate ActionBean has been identified the ActionResolver is also - * responsible for identifying the individual method on the ActionBean class that should handle - * this specific request.
+ * Resolvers are responsible for locating ActionBean instances that can handle the submitted + * request. Once an appropriate ActionBean has been identified the ActionResolver is also + * responsible for identifying the individual method on the ActionBean class that should handle this + * specific request. * *Throughout this class two terms are used that refer to similar but not interchangeable - * concepts. {@code UrlBinding} refers to the exact URL to which a bean is bound, e.g. - * {@code /account/Profile.action}. {@code Path} refers to the path segment of the requested - * URL and is generally composed of the URL binding and possibly some additional information, - * e.g. {@code /account/Profile.action/edit}. In general the methods in this class are capable - * of taking in a {@code path} and extracting the {@code UrlBinding} from it.
+ * concepts. {@code UrlBinding} refers to the exact URL to which a bean is bound, e.g. {@code + * /account/Profile.action}. {@code Path} refers to the path segment of the requested URL and is + * generally composed of the URL binding and possibly some additional information, e.g. {@code + * /account/Profile.action/edit}. In general the methods in this class are capable of taking in a + * {@code path} and extracting the {@code UrlBinding} from it. * * @author Tim Fennell */ public interface ActionResolver extends ConfigurableComponent { - /** - * Key that is to be used by ActionResolvers to store, as a request attribute, the - * action that was resolved in the current request. The 'action' stored is always a String. - */ - String RESOLVED_ACTION = "__stripes_resolved_action"; + /** + * Key that is to be used by ActionResolvers to store, as a request attribute, the action that was + * resolved in the current request. The 'action' stored is always a String. + */ + String RESOLVED_ACTION = "__stripes_resolved_action"; - /** - * Returns the URL binding that is a substring of the path provided. For example, if there - * is an ActionBean bound to {@code /user/Profile.action}, invoking - * {@code getUrlBindingFromPath("/user/Profile.action/view"} should return - * {@code "/user/Profile.action"}. - * - * @param path the path being used to access an ActionBean, either in a form or link tag, - * or in a request that is hitting the DispatcherServlet. - * @return the UrlBinding of the ActionBean appropriate for the request, or null if the path - * supplied cannot be mapped to an ActionBean. - */ - String getUrlBindingFromPath(String path); + /** + * Returns the URL binding that is a substring of the path provided. For example, if there is an + * ActionBean bound to {@code /user/Profile.action}, invoking {@code + * getUrlBindingFromPath("/user/Profile.action/view"} should return {@code + * "/user/Profile.action"}. + * + * @param path the path being used to access an ActionBean, either in a form or link tag, or in a + * request that is hitting the DispatcherServlet. + * @return the UrlBinding of the ActionBean appropriate for the request, or null if the path + * supplied cannot be mapped to an ActionBean. + */ + String getUrlBindingFromPath(String path); - /** - * Resolves the Class, implementing ActionBean, that should be used to handle the request. - * If more than one class can be mapped to the request the results of this method are undefined - - * implementations may return one of the implementations located or throw an exception. - * - * @param context the ActionBeanContext for the current request - * @return an instance of ActionBean to handle the current request - * @throws StripesServletException thrown if a ActionBean cannot be resolved for any reason - */ - ActionBean getActionBean(ActionBeanContext context) throws StripesServletException; + /** + * Resolves the Class, implementing ActionBean, that should be used to handle the request. If more + * than one class can be mapped to the request the results of this method are undefined - + * implementations may return one of the implementations located or throw an exception. + * + * @param context the ActionBeanContext for the current request + * @return an instance of ActionBean to handle the current request + * @throws StripesServletException thrown if a ActionBean cannot be resolved for any reason + */ + ActionBean getActionBean(ActionBeanContext context) throws StripesServletException; - /** - * Returns the ActionBean class that responds to the path provided. If the path does - * not contain a UrlBinding to which an ActionBean is bound a StripesServletException - * will be thrown. - * - * @param context the current action bean context - * @param path the path segment of the request (or link or action) - * @return an instance of ActionBean that is bound to the UrlBinding contained within - * the path supplied - * @throws StripesServletException if a matching ActionBean cannot be found - */ - ActionBean getActionBean(ActionBeanContext context, String path) - throws StripesServletException; + /** + * Returns the ActionBean class that responds to the path provided. If the path does not contain a + * UrlBinding to which an ActionBean is bound a StripesServletException will be thrown. + * + * @param context the current action bean context + * @param path the path segment of the request (or link or action) + * @return an instance of ActionBean that is bound to the UrlBinding contained within the path + * supplied + * @throws StripesServletException if a matching ActionBean cannot be found + */ + ActionBean getActionBean(ActionBeanContext context, String path) throws StripesServletException; - /** - * Fetches the Class representing the type of ActionBean that has been bound to - * the URL contained within the path supplied. Will not cause any ActionBean to be - * instantiated. If no ActionBean has been bound to the URL then null will be returned. - * - * @param path the path segment of a request url or form action or link - * @return the class object for the type of action bean bound to the url, or - * null if no bean is bound to that url - */ - Class extends ActionBean> getActionBeanType(String path); + /** + * Fetches the Class representing the type of ActionBean that has been bound to the URL contained + * within the path supplied. Will not cause any ActionBean to be instantiated. If no ActionBean + * has been bound to the URL then null will be returned. + * + * @param path the path segment of a request url or form action or link + * @return the class object for the type of action bean bound to the url, or null if no bean is + * bound to that url + */ + Class extends ActionBean> getActionBeanType(String path); - /** - * Takes a class that implements ActionBean and returns the URL binding of that class. - * Essentially the inverse of the getActionBean() methods, this method allows you to find - * out the URL binding of any ActionBean class. The binding can then be used to generate - * URLs or for any other purpose. If the bean is not bound, this method may return null. - * - * @param clazz a class that implements ActionBean - * @return the UrlBinding or null if none can be determined - * @since Stripes 1.2 - */ - String getUrlBinding(Class extends ActionBean> clazz); + /** + * Takes a class that implements ActionBean and returns the URL binding of that class. Essentially + * the inverse of the getActionBean() methods, this method allows you to find out the URL binding + * of any ActionBean class. The binding can then be used to generate URLs or for any other + * purpose. If the bean is not bound, this method may return null. + * + * @param clazz a class that implements ActionBean + * @return the UrlBinding or null if none can be determined + * @since Stripes 1.2 + */ + String getUrlBinding(Class extends ActionBean> clazz); - /** - * Determines the name of the event fired by the front end. Allows implementations to - * easiliy vary their strategy for specifying event names (e.g. button names, hidden field - * rewrites via JavaScript etc.). - * - * @param bean the ActionBean type that has been bound to the request - * @param context the ActionBeanContext for the current request - * @return the name of the event fired by the front end, or null if none is found - */ - String getEventName(Class extends ActionBean> bean, ActionBeanContext context); + /** + * Determines the name of the event fired by the front end. Allows implementations to easiliy vary + * their strategy for specifying event names (e.g. button names, hidden field rewrites via + * JavaScript etc.). + * + * @param bean the ActionBean type that has been bound to the request + * @param context the ActionBeanContext for the current request + * @return the name of the event fired by the front end, or null if none is found + */ + String getEventName(Class extends ActionBean> bean, ActionBeanContext context); - /** - * Resolves the Method which handles the named event. If more than one method is declared as - * able to handle the event the results of this method are undefined - implementations may - * return one of the implementations or throw an exception. - * - * @param bean the ActionBean type that has been bound to the request - * @param eventName the named event being handled by the ActionBean - * @return a Method object representing the handling method - never null - * @throws StripesServletException thrown if a method cannot be resolved for any reason, - * including but not limited to, when a Method does not exist that handles the event. - */ - Method getHandler(Class extends ActionBean> bean, String eventName) throws StripesServletException; + /** + * Resolves the Method which handles the named event. If more than one method is declared as able + * to handle the event the results of this method are undefined - implementations may return one + * of the implementations or throw an exception. + * + * @param bean the ActionBean type that has been bound to the request + * @param eventName the named event being handled by the ActionBean + * @return a Method object representing the handling method - never null + * @throws StripesServletException thrown if a method cannot be resolved for any reason, including + * but not limited to, when a Method does not exist that handles the event. + */ + Method getHandler(Class extends ActionBean> bean, String eventName) + throws StripesServletException; - /** - * Locates and returns the default handler method that should be invoked when no specific - * event is named. This occurs most often when a user submits a form via the Enter button - * and no button or image button name is passed. - * - * @param bean the ActionBean type that has been bound to the request - * @return a Method object representing the handling method - never null - * @throws StripesServletException thrown if a default handler method cannot be found. - */ - Method getDefaultHandler(Class extends ActionBean> bean) throws StripesServletException; + /** + * Locates and returns the default handler method that should be invoked when no specific event is + * named. This occurs most often when a user submits a form via the Enter button and no button or + * image button name is passed. + * + * @param bean the ActionBean type that has been bound to the request + * @return a Method object representing the handling method - never null + * @throws StripesServletException thrown if a default handler method cannot be found. + */ + Method getDefaultHandler(Class extends ActionBean> bean) throws StripesServletException; - /** - * Returns the name of the event to which a given handler method responds. Primarily useful - * when the default event is fired and it is necessary to figure out if the handler also - * responds to a named event. - * - * @param handler the handler method who's event name to find - * @return String the name of the event handled by this method, or null if an event is - * not mapped to this method. - */ - String getHandledEvent(Method handler) throws StripesServletException; + /** + * Returns the name of the event to which a given handler method responds. Primarily useful when + * the default event is fired and it is necessary to figure out if the handler also responds to a + * named event. + * + * @param handler the handler method who's event name to find + * @return String the name of the event handled by this method, or null if an event is not mapped + * to this method. + */ + String getHandledEvent(Method handler) throws StripesServletException; - /** - * Get all the classes implementing {@link ActionBean} that are recognized by this - * {@link ActionResolver}. This method must return the full set of {@link ActionBean} classes - * after the call to init(). - */ - Collection> getActionBeanClasses(); + /** + * Get all the classes implementing {@link ActionBean} that are recognized by this {@link + * ActionResolver}. This method must return the full set of {@link ActionBean} classes after the + * call to init(). + */ + Collection > getActionBeanClasses(); - /** - * Gets the {@link ActionBean} type that matches actionBeanName
. Implementers may use different - * strategies for naming {@link ActionBean}s. This method can return null if no - * action beans exist with the givenactionBeanName
or multiple action beans resolve to the same - *actionBeanName
- * - * @param actionBeanName The name that identifies the {@link ActionBean} - * @return the ActionBean class that matches actionBeanName, or null if an ActionBean can't be resolved for the name - */ - Class extends ActionBean> getActionBeanByName(String actionBeanName); -} \ No newline at end of file + /** + * Gets the {@link ActionBean} type that matchesactionBeanName
. Implementers may use + * different strategies for naming {@link ActionBean}s. This method can return null if no action + * beans exist with the givenactionBeanName
or multiple action beans resolve to the + * sameactionBeanName
+ * + * @param actionBeanName The name that identifies the {@link ActionBean} + * @return the ActionBean class that matches actionBeanName, or null if an ActionBean can't be + * resolved for the name + */ + Class extends ActionBean> getActionBeanByName(String actionBeanName); +} diff --git a/stripes/src/main/java/net/sourceforge/stripes/controller/AnnotatedClassActionResolver.java b/stripes/src/main/java/net/sourceforge/stripes/controller/AnnotatedClassActionResolver.java index 5d9017fc6..36f3efd81 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/controller/AnnotatedClassActionResolver.java +++ b/stripes/src/main/java/net/sourceforge/stripes/controller/AnnotatedClassActionResolver.java @@ -14,6 +14,19 @@ */ package net.sourceforge.stripes.controller; +import jakarta.servlet.http.HttpServletRequest; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import net.sourceforge.stripes.action.ActionBean; import net.sourceforge.stripes.action.ActionBeanContext; import net.sourceforge.stripes.action.DefaultHandler; @@ -30,645 +43,643 @@ import net.sourceforge.stripes.util.ResolverUtil; import net.sourceforge.stripes.util.StringUtil; -import javax.servlet.http.HttpServletRequest; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - /** - *Uses Annotations on classes to identify the ActionBean that corresponds to the current - * request. ActionBeans are annotated with an {@code @UrlBinding} annotation, which denotes the - * web application relative URL that the ActionBean should respond to.
+ * Uses Annotations on classes to identify the ActionBean that corresponds to the current request. + * ActionBeans are annotated with an {@code @UrlBinding} annotation, which denotes the web + * application relative URL that the ActionBean should respond to. * *Individual methods on ActionBean classes are expected to be annotated with @HandlesEvent - * annotations, and potentially a @DefaultHandler annotation. Using these annotations the - * Resolver will determine which method should be executed for the current request.
+ * annotations, and potentially a @DefaultHandler annotation. Using these annotations the Resolver + * will determine which method should be executed for the current request. * * @see net.sourceforge.stripes.action.UrlBinding * @author Tim Fennell */ public class AnnotatedClassActionResolver implements ActionResolver { - /** - * Configuration key used to lookup a comma-separated list of package names. The - * packages (and their sub-packages) will be scanned for implementations of - * ActionBean. - * @since Stripes 1.5 - */ - public static final String PACKAGES = "ActionResolver.Packages"; - - /** Key used to store the default handler in the Map of handler methods. */ - private static final String DEFAULT_HANDLER_KEY = "__default_handler"; - - /** Log instance for use within in this class. */ - private static final Log log = Log.getInstance(AnnotatedClassActionResolver.class); - - /** Handle to the configuration. */ - private Configuration configuration; - - /** Parses {@link UrlBinding} values and maps request URLs to {@link ActionBean}s. */ - private UrlBindingFactory urlBindingFactory = new UrlBindingFactory(); - - /** Maps action bean classes simple name -> action bean class */ - protected final Map> actionBeansByName = - new ConcurrentHashMap >(); - - /** - * Map used to resolve the methods handling events within form beans. Maps the class - * representing a subclass of ActionBean to a Map of event names to Method objects. - */ - private Map ,Map > eventMappings = - new HashMap ,Map >() { - private static final long serialVersionUID = 1L; - - @Override - public Map get(Object key) { - Map value = super.get(key); - if (value == null) - return Collections.emptyMap(); - else - return value; - } - }; - - /** - * Scans the classpath of the current classloader (not including parents) to find implementations - * of the ActionBean interface. Examines annotations on the classes found to determine what - * forms and events they map to, and stores this information in a pair of maps for fast - * access during request processing. - */ - public void init(Configuration configuration) throws Exception { - this.configuration = configuration; - - // Process each ActionBean - for (Class extends ActionBean> clazz : findClasses()) { - addActionBean(clazz); + /** + * Configuration key used to lookup a comma-separated list of package names. The packages (and + * their sub-packages) will be scanned for implementations of ActionBean. + * + * @since Stripes 1.5 + */ + public static final String PACKAGES = "ActionResolver.Packages"; + + /** Key used to store the default handler in the Map of handler methods. */ + private static final String DEFAULT_HANDLER_KEY = "__default_handler"; + + /** Log instance for use within in this class. */ + private static final Log log = Log.getInstance(AnnotatedClassActionResolver.class); + + /** Handle to the configuration. */ + private Configuration configuration; + + /** Parses {@link UrlBinding} values and maps request URLs to {@link ActionBean}s. */ + private UrlBindingFactory urlBindingFactory = new UrlBindingFactory(); + + /** Maps action bean classes simple name -> action bean class */ + protected final Map > actionBeansByName = + new ConcurrentHashMap >(); + + /** + * Map used to resolve the methods handling events within form beans. Maps the class representing + * a subclass of ActionBean to a Map of event names to Method objects. + */ + private Map , Map > eventMappings = + new HashMap , Map >() { + private static final long serialVersionUID = 1L; + + @Override + public Map get(Object key) { + Map value = super.get(key); + if (value == null) return Collections.emptyMap(); + else return value; } - - addBeanNameMappings(); + }; + + /** + * Scans the classpath of the current classloader (not including parents) to find implementations + * of the ActionBean interface. Examines annotations on the classes found to determine what forms + * and events they map to, and stores this information in a pair of maps for fast access during + * request processing. + */ + public void init(Configuration configuration) throws Exception { + this.configuration = configuration; + + // Process each ActionBean + for (Class extends ActionBean> clazz : findClasses()) { + addActionBean(clazz); } - protected void addBeanNameMappings() { - Set foundBeanNames = new HashSet (); - for (Class extends ActionBean> clazz : getActionBeanClasses()) { - if (foundBeanNames.contains(clazz.getSimpleName())) { - log.warn("Found multiple action beans with the same simple name: ", clazz.getSimpleName(), ". You will " + - "need to reference these action beans by their fully qualified names"); - actionBeansByName.remove(clazz.getSimpleName()); - continue; - } - - foundBeanNames.add(clazz.getSimpleName()); - actionBeansByName.put(clazz.getSimpleName(), clazz); - } + addBeanNameMappings(); + } + + protected void addBeanNameMappings() { + Set foundBeanNames = new HashSet (); + for (Class extends ActionBean> clazz : getActionBeanClasses()) { + if (foundBeanNames.contains(clazz.getSimpleName())) { + log.warn( + "Found multiple action beans with the same simple name: ", + clazz.getSimpleName(), + ". You will " + "need to reference these action beans by their fully qualified names"); + actionBeansByName.remove(clazz.getSimpleName()); + continue; + } + + foundBeanNames.add(clazz.getSimpleName()); + actionBeansByName.put(clazz.getSimpleName(), clazz); } - - /** Get the {@link UrlBindingFactory} that is being used by this action resolver. */ - public UrlBindingFactory getUrlBindingFactory() { - return urlBindingFactory; + } + + /** Get the {@link UrlBindingFactory} that is being used by this action resolver. */ + public UrlBindingFactory getUrlBindingFactory() { + return urlBindingFactory; + } + + /** + * Adds an ActionBean class to the set that this resolver can resolve. Identifies the URL binding + * and the events managed by the class and stores them in Maps for fast lookup. + * + * @param clazz a class that implements ActionBean + */ + protected void addActionBean(Class extends ActionBean> clazz) { + // Ignore abstract classes + if (Modifier.isAbstract(clazz.getModifiers()) || clazz.isAnnotationPresent(DontAutoLoad.class)) + return; + + String binding = getUrlBinding(clazz); + if (binding == null) return; + + // make sure mapping exists in cache + UrlBinding proto = getUrlBindingFactory().getBindingPrototype(clazz); + if (proto == null) { + getUrlBindingFactory().addBinding(clazz, new UrlBinding(clazz, binding)); } - /** - * Adds an ActionBean class to the set that this resolver can resolve. Identifies - * the URL binding and the events managed by the class and stores them in Maps - * for fast lookup. - * - * @param clazz a class that implements ActionBean - */ - protected void addActionBean(Class extends ActionBean> clazz) { - // Ignore abstract classes - if (Modifier.isAbstract(clazz.getModifiers()) || clazz.isAnnotationPresent(DontAutoLoad.class)) - return; - - String binding = getUrlBinding(clazz); - if (binding == null) - return; - - // make sure mapping exists in cache - UrlBinding proto = getUrlBindingFactory().getBindingPrototype(clazz); - if (proto == null) { - getUrlBindingFactory().addBinding(clazz, new UrlBinding(clazz, binding)); - } - - // Construct the mapping of event->method for the class - Map classMappings = new HashMap (); - processMethods(clazz, classMappings); + // Construct the mapping of event->method for the class + Map classMappings = new HashMap (); + processMethods(clazz, classMappings); - // Put the event->method mapping for the class into the set of mappings - this.eventMappings.put(clazz, classMappings); + // Put the event->method mapping for the class into the set of mappings + this.eventMappings.put(clazz, classMappings); - if (proto != null) { - proto.initDefaultValueWithDefaultHandlerIfNeeded(this); - } - - if (log.getRealLog().isDebugEnabled()) { - // Print out the event mappings nicely - for (Map.Entry entry : classMappings.entrySet()) { - String event = entry.getKey(); - Method handler = entry.getValue(); - boolean isDefault = DEFAULT_HANDLER_KEY.equals(event); - - log.debug("Bound: ", clazz.getSimpleName(), ".", handler.getName(), "() ==> ", - binding, isDefault ? "" : "?" + event); - } - } + if (proto != null) { + proto.initDefaultValueWithDefaultHandlerIfNeeded(this); } - /** - * Removes an ActionBean class from the set that this resolver can resolve. The URL binding - * and the events managed by the class are removed from the cache. - * - * @param clazz a class that implements ActionBean - */ - protected void removeActionBean(Class extends ActionBean> clazz) { - String binding = getUrlBinding(clazz); - if (binding != null) { - getUrlBindingFactory().removeBinding(clazz); - } - eventMappings.remove(clazz); + if (log.getRealLog().isDebugEnabled()) { + // Print out the event mappings nicely + for (Map.Entry entry : classMappings.entrySet()) { + String event = entry.getKey(); + Method handler = entry.getValue(); + boolean isDefault = DEFAULT_HANDLER_KEY.equals(event); + + log.debug( + "Bound: ", + clazz.getSimpleName(), + ".", + handler.getName(), + "() ==> ", + binding, + isDefault ? "" : "?" + event); + } } - - /** - * Returns the URL binding that is a substring of the path provided. For example, if there - * is an ActionBean bound to {@code /user/Profile.action} the path - * {@code /user/Profile.action/view} would return {@code /user/Profile.action}. - * - * @param path the path being used to access an ActionBean, either in a form or link tag, - * or in a request that is hitting the DispatcherServlet. - * @return the UrlBinding of the ActionBean appropriate for the request, or null if the path - * supplied cannot be mapped to an ActionBean. - */ - public String getUrlBindingFromPath(String path) { - UrlBinding mapping = getUrlBindingFactory().getBindingPrototype(path); - return mapping == null ? null : mapping.toString(); + } + + /** + * Removes an ActionBean class from the set that this resolver can resolve. The URL binding and + * the events managed by the class are removed from the cache. + * + * @param clazz a class that implements ActionBean + */ + protected void removeActionBean(Class extends ActionBean> clazz) { + String binding = getUrlBinding(clazz); + if (binding != null) { + getUrlBindingFactory().removeBinding(clazz); } - - /** - * Takes a class that implements ActionBean and returns the URL binding of that class. - * The default implementation retrieves the UrlBinding annotations and returns its - * value. Subclasses could do more complex things like parse the class and package names - * and construct a "default" binding when one is not specified. - * - * @param clazz a class that implements ActionBean - * @return the UrlBinding or null if none can be determined - */ - public String getUrlBinding(Class extends ActionBean> clazz) { - UrlBinding mapping = getUrlBindingFactory().getBindingPrototype(clazz); - return mapping == null ? null : mapping.toString(); + eventMappings.remove(clazz); + } + + /** + * Returns the URL binding that is a substring of the path provided. For example, if there is an + * ActionBean bound to {@code /user/Profile.action} the path {@code /user/Profile.action/view} + * would return {@code /user/Profile.action}. + * + * @param path the path being used to access an ActionBean, either in a form or link tag, or in a + * request that is hitting the DispatcherServlet. + * @return the UrlBinding of the ActionBean appropriate for the request, or null if the path + * supplied cannot be mapped to an ActionBean. + */ + public String getUrlBindingFromPath(String path) { + UrlBinding mapping = getUrlBindingFactory().getBindingPrototype(path); + return mapping == null ? null : mapping.toString(); + } + + /** + * Takes a class that implements ActionBean and returns the URL binding of that class. The default + * implementation retrieves the UrlBinding annotations and returns its value. Subclasses could do + * more complex things like parse the class and package names and construct a "default" binding + * when one is not specified. + * + * @param clazz a class that implements ActionBean + * @return the UrlBinding or null if none can be determined + */ + public String getUrlBinding(Class extends ActionBean> clazz) { + UrlBinding mapping = getUrlBindingFactory().getBindingPrototype(clazz); + return mapping == null ? null : mapping.toString(); + } + + /** + * Helper method that examines a class, starting at it's highest super class and working it's way + * down again, to find method annotations and ensure that child class annotations take precedence. + */ + protected void processMethods(Class> clazz, Map classMappings) { + // Do the super class first if there is one + Class> superclass = clazz.getSuperclass(); + if (superclass != null) { + processMethods(superclass, classMappings); } - /** - * Helper method that examines a class, starting at it's highest super class and - * working it's way down again, to find method annotations and ensure that child - * class annotations take precedence. - */ - protected void processMethods(Class> clazz, Map classMappings) { - // Do the super class first if there is one - Class> superclass = clazz.getSuperclass(); - if (superclass != null) { - processMethods(superclass, classMappings); + Method[] methods = clazz.getDeclaredMethods(); + for (Method method : methods) { + if (Modifier.isPublic(method.getModifiers()) && !method.isBridge()) { + String eventName = getHandledEvent(method); + + // look for duplicate event names within the current class + if (classMappings.containsKey(eventName) + && clazz.equals(classMappings.get(eventName).getDeclaringClass())) { + throw new StripesRuntimeException( + "The ActionBean " + + clazz + + " declares multiple event handlers for event '" + + eventName + + "'"); } - Method[] methods = clazz.getDeclaredMethods(); - for (Method method : methods) { - if ( Modifier.isPublic(method.getModifiers()) && !method.isBridge() ) { - String eventName = getHandledEvent(method); - - // look for duplicate event names within the current class - if (classMappings.containsKey(eventName) - && clazz.equals(classMappings.get(eventName).getDeclaringClass())) { - throw new StripesRuntimeException("The ActionBean " + clazz - + " declares multiple event handlers for event '" + eventName + "'"); - } - - DefaultHandler defaultMapping = method.getAnnotation(DefaultHandler.class); - if (eventName != null) { - classMappings.put(eventName, method); - } - if (defaultMapping != null) { - // look for multiple default handlers within the current class - if (classMappings.containsKey(DEFAULT_HANDLER_KEY) - && clazz.equals(classMappings.get(DEFAULT_HANDLER_KEY).getDeclaringClass())) { - throw new StripesRuntimeException("The ActionBean " + clazz - + " declares multiple default event handlers"); - } - - // Makes sure we catch the default handler - classMappings.put(DEFAULT_HANDLER_KEY, method); - } - } + DefaultHandler defaultMapping = method.getAnnotation(DefaultHandler.class); + if (eventName != null) { + classMappings.put(eventName, method); } - } + if (defaultMapping != null) { + // look for multiple default handlers within the current class + if (classMappings.containsKey(DEFAULT_HANDLER_KEY) + && clazz.equals(classMappings.get(DEFAULT_HANDLER_KEY).getDeclaringClass())) { + throw new StripesRuntimeException( + "The ActionBean " + clazz + " declares multiple default event handlers"); + } - /** - * Responsible for determining the name of the event handled by this method, if indeed - * it handles one at all. By default looks for the HandlesEvent annotations and returns - * it's value if present. - * - * @param handler a method that might or might not be a handler method - * @return the name of the event handled, or null - */ - public String getHandledEvent(Method handler) { - HandlesEvent mapping = handler.getAnnotation(HandlesEvent.class); - if (mapping != null) { - return mapping.value(); - } - else { - return null; + // Makes sure we catch the default handler + classMappings.put(DEFAULT_HANDLER_KEY, method); } + } } - - /** - * Fetches the Class representing the type of ActionBean that would respond were a - * request made with the path specified. Checks to see if the full path matches any - * bean's UrlBinding. If no ActionBean matches then successively removes path segments - * (separated by slashes) from the end of the path until a match is found.
- * - * @param path the path segment of a URL - * @return the Class object for the type of action bean that will respond if a request - * is made using the path specified or null if no ActionBean matches. - */ - public Class extends ActionBean> getActionBeanType(String path) { - UrlBinding binding = getUrlBindingFactory().getBindingPrototype(path); - return binding == null ? null : binding.getBeanType(); + } + + /** + * Responsible for determining the name of the event handled by this method, if indeed it handles + * one at all. By default looks for the HandlesEvent annotations and returns it's value if + * present. + * + * @param handler a method that might or might not be a handler method + * @return the name of the event handled, or null + */ + public String getHandledEvent(Method handler) { + HandlesEvent mapping = handler.getAnnotation(HandlesEvent.class); + if (mapping != null) { + return mapping.value(); + } else { + return null; } - - /** - * Gets the logical name of the ActionBean that should handle the request. Implemented to look - * up the name of the form based on the name assigned to the form in the form tag, and - * encoded in a hidden field. - * - * @param context the ActionBeanContext for the current request - * @return the name of the form to be used for this request - */ - public ActionBean getActionBean(ActionBeanContext context) throws StripesServletException { - HttpServletRequest request = context.getRequest(); - String path = HttpUtil.getRequestedPath(request); - ActionBean bean = getActionBean(context, path); - request.setAttribute(RESOLVED_ACTION, getUrlBindingFromPath(path)); - return bean; + } + + /** + * Fetches the Class representing the type of ActionBean that would respond were a request made + * with the path specified. Checks to see if the full path matches any bean's UrlBinding. If no + * ActionBean matches then successively removes path segments (separated by slashes) from the end + * of the path until a match is found. + * + * @param path the path segment of a URL + * @return the Class object for the type of action bean that will respond if a request is made + * using the path specified or null if no ActionBean matches. + */ + public Class extends ActionBean> getActionBeanType(String path) { + UrlBinding binding = getUrlBindingFactory().getBindingPrototype(path); + return binding == null ? null : binding.getBeanType(); + } + + /** + * Gets the logical name of the ActionBean that should handle the request. Implemented to look up + * the name of the form based on the name assigned to the form in the form tag, and encoded in a + * hidden field. + * + * @param context the ActionBeanContext for the current request + * @return the name of the form to be used for this request + */ + public ActionBean getActionBean(ActionBeanContext context) throws StripesServletException { + HttpServletRequest request = context.getRequest(); + String path = HttpUtil.getRequestedPath(request); + ActionBean bean = getActionBean(context, path); + request.setAttribute(RESOLVED_ACTION, getUrlBindingFromPath(path)); + return bean; + } + + /** + * Returns the ActionBean class that is bound to the UrlBinding supplied. If the action bean + * already exists in the appropriate scope (request or session) then the existing instance will be + * supplied. If not, then a new instance will be manufactured and have the supplied + * ActionBeanContext set on it. + * + * @param path a URL to which an ActionBean is bound, or a path starting with the URL to which an + * ActionBean has been bound. + * @param context the current ActionBeanContext + * @return a Classfor the ActionBean requested + * @throws StripesServletException if the UrlBinding does not match an ActionBean binding + */ + public ActionBean getActionBean(ActionBeanContext context, String path) + throws StripesServletException { + Class extends ActionBean> beanClass = getActionBeanType(path); + ActionBean bean; + + if (beanClass == null) { + throw new ActionBeanNotFoundException(path, getUrlBindingFactory().getPathMap()); } - /** - * Returns the ActionBean class that is bound to the UrlBinding supplied. If the action - * bean already exists in the appropriate scope (request or session) then the existing - * instance will be supplied. If not, then a new instance will be manufactured and have - * the supplied ActionBeanContext set on it. - * - * @param path a URL to which an ActionBean is bound, or a path starting with the URL - * to which an ActionBean has been bound. - * @param context the current ActionBeanContext - * @return a Class for the ActionBean requested - * @throws StripesServletException if the UrlBinding does not match an ActionBean binding - */ - public ActionBean getActionBean(ActionBeanContext context, String path) throws StripesServletException { - Class extends ActionBean> beanClass = getActionBeanType(path); - ActionBean bean; - - if (beanClass == null) { - throw new ActionBeanNotFoundException(path, getUrlBindingFactory().getPathMap()); - } - - String bindingPath = getUrlBinding(beanClass); - try { - HttpServletRequest request = context.getRequest(); - - if (beanClass.isAnnotationPresent(SessionScope.class)) { - bean = (ActionBean) request.getSession().getAttribute(bindingPath); - - if (bean == null) { - bean = makeNewActionBean(beanClass, context); - request.getSession().setAttribute(bindingPath, bean); - } - } - else { - bean = (ActionBean) request.getAttribute(bindingPath); - if (bean == null) { - bean = makeNewActionBean(beanClass, context); - request.setAttribute(bindingPath, bean); - } - } - - setActionBeanContext(bean, context); - } - catch (Exception e) { - StripesServletException sse = new StripesServletException( - "Could not create instance of ActionBean type [" + beanClass.getName() + "].", e); - throw sse; - } - - assertGetContextWorks(bean); - return bean; + String bindingPath = getUrlBinding(beanClass); + try { + HttpServletRequest request = context.getRequest(); - } + if (beanClass.isAnnotationPresent(SessionScope.class)) { + bean = (ActionBean) request.getSession().getAttribute(bindingPath); - /** - * Calls {@link ActionBean#setContext(ActionBeanContext)} with the given {@code context} only if - * necessary. Subclasses should use this method instead of setting the context directly because - * it can be somewhat tricky to determine when it needs to be done. - * - * @param bean The bean whose context may need to be set. - * @param context The context to pass to the bean if necessary. - */ - protected void setActionBeanContext(ActionBean bean, ActionBeanContext context) { - ActionBeanContext abcFromBean = bean.getContext(); - if (abcFromBean == null) { - bean.setContext(context); + if (bean == null) { + bean = makeNewActionBean(beanClass, context); + request.getSession().setAttribute(bindingPath, bean); } - else { - StripesRequestWrapper wrapperFromBean = StripesRequestWrapper - .findStripesWrapper(abcFromBean.getRequest()); - StripesRequestWrapper wrapperFromRequest = StripesRequestWrapper - .findStripesWrapper(context.getRequest()); - if (wrapperFromBean != wrapperFromRequest) - bean.setContext(context); - } - } - - /** - * Since many down stream parts of Stripes rely on the ActionBean properly returning the - * context it is given, we'll just test it up front. Called after the bean is instantiated. - * - * @param bean the ActionBean to test to see if getContext() works correctly - * @throws StripesServletException if getContext() returns null - */ - protected void assertGetContextWorks(final ActionBean bean) throws StripesServletException { - if (bean.getContext() == null) { - throw new StripesServletException("Ahem. Stripes has just resolved and instantiated " + - "the ActionBean class " + bean.getClass().getName() + " and set the ActionBeanContext " + - "on it. However calling getContext() isn't returning the context back! Since " + - "this is required for several parts of Stripes to function correctly you should " + - "now stop and implement setContext()/getContext() correctly. Thank you."); + } else { + bean = (ActionBean) request.getAttribute(bindingPath); + if (bean == null) { + bean = makeNewActionBean(beanClass, context); + request.setAttribute(bindingPath, bean); } + } + + setActionBeanContext(bean, context); + } catch (Exception e) { + StripesServletException sse = + new StripesServletException( + "Could not create instance of ActionBean type [" + beanClass.getName() + "].", e); + throw sse; } - /** - * Helper method to construct and return a new ActionBean instance. Called whenever a new - * instance needs to be manufactured. Provides a convenient point for subclasses to add - * specific behaviour during action bean creation. - * - * @param type the type of ActionBean to create - * @param context the current ActionBeanContext - * @return the new ActionBean instance - * @throws Exception if anything goes wrong! - */ - protected ActionBean makeNewActionBean(Class extends ActionBean> type, ActionBeanContext context) - throws Exception { - - return getConfiguration().getObjectFactory().newInstance(type); + assertGetContextWorks(bean); + return bean; + } + + /** + * Calls {@link ActionBean#setContext(ActionBeanContext)} with the given {@code context} only if + * necessary. Subclasses should use this method instead of setting the context directly because it + * can be somewhat tricky to determine when it needs to be done. + * + * @param bean The bean whose context may need to be set. + * @param context The context to pass to the bean if necessary. + */ + protected void setActionBeanContext(ActionBean bean, ActionBeanContext context) { + ActionBeanContext abcFromBean = bean.getContext(); + if (abcFromBean == null) { + bean.setContext(context); + } else { + StripesRequestWrapper wrapperFromBean = + StripesRequestWrapper.findStripesWrapper(abcFromBean.getRequest()); + StripesRequestWrapper wrapperFromRequest = + StripesRequestWrapper.findStripesWrapper(context.getRequest()); + if (wrapperFromBean != wrapperFromRequest) bean.setContext(context); } - - - /** - * - * Try various means to determine which event is to be executed on the current ActionBean. If a - * 'special' request attribute ({@link StripesConstants#REQ_ATTR_EVENT_NAME}) is present in - * the request, then return its value. This attribute is used to handle internal forwards, when - * request parameters are merged and cannot reliably determine the desired event name. - *
- * - *- * If that doesn't work, the value of a 'special' request parameter ({@link StripesConstants#URL_KEY_EVENT_NAME}) - * is checked to see if contains a single value matching an event name. - *
- * - *- * Failing that, search for a parameter in the request whose name matches one of the named - * events handled by the ActionBean. For example, if the ActionBean can handle events foo and - * bar, this method will scan the request for foo=somevalue and bar=somevalue. If it finds a - * request parameter with a matching name it will return that name. If there are multiple - * matching names, the result of this method cannot be guaranteed and a - * {@link StripesRuntimeException} will be thrown. - *
- * - *- * Finally, if the event name cannot be determined through the parameter names and there is - * extra path information beyond the URL binding of the ActionBean, it is checked to see if it - * matches an event name. - *
- * - * @param bean the ActionBean type bound to the request - * @param context the ActionBeanContect for the current request - * @return String the name of the event submitted, or null if none can be found - */ - public String getEventName(Class extends ActionBean> bean, ActionBeanContext context) { - String event = getEventNameFromRequestAttribute(bean, context); - if (event == null) event = getEventNameFromEventNameParam(bean, context); - if (event == null) event = getEventNameFromRequestParams(bean, context); - if (event == null) event = getEventNameFromPath(bean, context); - return event; + } + + /** + * Since many down stream parts of Stripes rely on the ActionBean properly returning the context + * it is given, we'll just test it up front. Called after the bean is instantiated. + * + * @param bean the ActionBean to test to see if getContext() works correctly + * @throws StripesServletException if getContext() returns null + */ + protected void assertGetContextWorks(final ActionBean bean) throws StripesServletException { + if (bean.getContext() == null) { + throw new StripesServletException( + "Ahem. Stripes has just resolved and instantiated " + + "the ActionBean class " + + bean.getClass().getName() + + " and set the ActionBeanContext " + + "on it. However calling getContext() isn't returning the context back! Since " + + "this is required for several parts of Stripes to function correctly you should " + + "now stop and implement setContext()/getContext() correctly. Thank you."); } - - /** - * Checks a special request attribute to get the event name. This attribute - * may be set when the presence of the original request parameters on a - * forwarded request makes it difficult to determine which event to fire. - * - * @param bean the ActionBean type bound to the request - * @param context the ActionBeanContect for the current request - * @return the name of the event submitted, or null if none can be found - * @see StripesConstants#REQ_ATTR_EVENT_NAME - */ - protected String getEventNameFromRequestAttribute( - Class extends ActionBean> bean, ActionBeanContext context) { - return (String) context.getRequest().getAttribute( - StripesConstants.REQ_ATTR_EVENT_NAME); + } + + /** + * Helper method to construct and return a new ActionBean instance. Called whenever a new instance + * needs to be manufactured. Provides a convenient point for subclasses to add specific behaviour + * during action bean creation. + * + * @param type the type of ActionBean to create + * @param context the current ActionBeanContext + * @return the new ActionBean instance + * @throws Exception if anything goes wrong! + */ + protected ActionBean makeNewActionBean( + Class extends ActionBean> type, ActionBeanContext context) throws Exception { + + return getConfiguration().getObjectFactory().newInstance(type); + } + + /** + * Try various means to determine which event is to be executed on the current ActionBean. If a + * 'special' request attribute ({@link StripesConstants#REQ_ATTR_EVENT_NAME}) is present in the + * request, then return its value. This attribute is used to handle internal forwards, when + * request parameters are merged and cannot reliably determine the desired event name. + * + *If that doesn't work, the value of a 'special' request parameter ({@link + * StripesConstants#URL_KEY_EVENT_NAME}) is checked to see if contains a single value matching an + * event name. + * + *
Failing that, search for a parameter in the request whose name matches one of the named + * events handled by the ActionBean. For example, if the ActionBean can handle events foo and bar, + * this method will scan the request for foo=somevalue and bar=somevalue. If it finds a request + * parameter with a matching name it will return that name. If there are multiple matching names, + * the result of this method cannot be guaranteed and a {@link StripesRuntimeException} will be + * thrown. + * + *
Finally, if the event name cannot be determined through the parameter names and there is + * extra path information beyond the URL binding of the ActionBean, it is checked to see if it + * matches an event name. + * + * @param bean the ActionBean type bound to the request + * @param context the ActionBeanContect for the current request + * @return String the name of the event submitted, or null if none can be found + */ + public String getEventName(Class extends ActionBean> bean, ActionBeanContext context) { + String event = getEventNameFromRequestAttribute(bean, context); + if (event == null) event = getEventNameFromEventNameParam(bean, context); + if (event == null) event = getEventNameFromRequestParams(bean, context); + if (event == null) event = getEventNameFromPath(bean, context); + return event; + } + + /** + * Checks a special request attribute to get the event name. This attribute may be set when the + * presence of the original request parameters on a forwarded request makes it difficult to + * determine which event to fire. + * + * @param bean the ActionBean type bound to the request + * @param context the ActionBeanContect for the current request + * @return the name of the event submitted, or null if none can be found + * @see StripesConstants#REQ_ATTR_EVENT_NAME + */ + protected String getEventNameFromRequestAttribute( + Class extends ActionBean> bean, ActionBeanContext context) { + return (String) context.getRequest().getAttribute(StripesConstants.REQ_ATTR_EVENT_NAME); + } + + /** + * Loops through the set of known events for the ActionBean to see if the event names are present + * as parameter names in the request. Returns the first event name found in the request, or null + * if none is found. + * + * @param bean the ActionBean type bound to the request + * @param context the ActionBeanContext for the current request + * @return String the name of the event submitted, or null if none can be found + */ + @SuppressWarnings("unchecked") + protected String getEventNameFromRequestParams( + Class extends ActionBean> bean, ActionBeanContext context) { + + List
eventParams = new ArrayList (); + Map parameterMap = context.getRequest().getParameterMap(); + for (String event : this.eventMappings.get(bean).keySet()) { + if (parameterMap.containsKey(event) || parameterMap.containsKey(event + ".x")) { + eventParams.add(event); + } } - /** - * Loops through the set of known events for the ActionBean to see if the event - * names are present as parameter names in the request. Returns the first event - * name found in the request, or null if none is found. - * - * @param bean the ActionBean type bound to the request - * @param context the ActionBeanContext for the current request - * @return String the name of the event submitted, or null if none can be found - */ - @SuppressWarnings("unchecked") - protected String getEventNameFromRequestParams(Class extends ActionBean> bean, - ActionBeanContext context) { - - List eventParams = new ArrayList (); - Map parameterMap = context.getRequest().getParameterMap(); - for (String event : this.eventMappings.get(bean).keySet()) { - if (parameterMap.containsKey(event) || parameterMap.containsKey(event + ".x")) { - eventParams.add(event); - } - } - - if (eventParams.size() == 0) { - return null; - } - else if (eventParams.size() == 1) { - return eventParams.get(0); - } - else { - throw new StripesRuntimeException("Multiple event parameters " + eventParams - + " are present in this request. Only one event parameter may be specified " - + "per request. Otherwise, Stripes would be unable to determine which event " - + "to execute."); - } + if (eventParams.size() == 0) { + return null; + } else if (eventParams.size() == 1) { + return eventParams.get(0); + } else { + throw new StripesRuntimeException( + "Multiple event parameters " + + eventParams + + " are present in this request. Only one event parameter may be specified " + + "per request. Otherwise, Stripes would be unable to determine which event " + + "to execute."); } - - /** - * Looks to see if there is extra path information beyond simply the url binding of the - * bean. If it does and the next /-separated part of the path matches one of the known - * event names for the bean, that event name will be returned, otherwise null. - * - * @param bean the ActionBean type bound to the request - * @param context the ActionBeanContect for the current request - * @return String the name of the event submitted, or null if none can be found - */ - protected String getEventNameFromPath(Class extends ActionBean> bean, - ActionBeanContext context) { - Map mappings = this.eventMappings.get(bean); - String path = HttpUtil.getRequestedPath(context.getRequest()); - UrlBinding prototype = getUrlBindingFactory().getBindingPrototype(path); - String binding = prototype == null ? null : prototype.getPath(); - - if (binding != null && path.length() != binding.length()) { - String extra = path.substring(binding.length() + 1); - int index = extra.indexOf("/"); - String event = extra.substring(0, (index != -1) ? index : extra.length()); - if (mappings.containsKey(event)) { - return event; - } - } - - return null; - } - - /** - * Looks to see if there is a single non-empty parameter value for the parameter name - * specified by {@link StripesConstants#URL_KEY_EVENT_NAME}. If there is, and it - * matches a known event it is returned, otherwise returns null. - * - * @param bean the ActionBean type bound to the request - * @param context the ActionBeanContect for the current request - * @return String the name of the event submitted, or null if none can be found - */ - protected String getEventNameFromEventNameParam(Class extends ActionBean> bean, - ActionBeanContext context) { - String[] values = context.getRequest().getParameterValues(StripesConstants.URL_KEY_EVENT_NAME); - String event = null; - if (values != null && values.length == 1 && this.eventMappings.get(bean).containsKey(values[0])) { - event = values[0]; - } - - // Warn of non-backward-compatible behavior - if (event != null) { - try { - String otherName = getEventNameFromRequestParams(bean, context); - if (otherName != null && !otherName.equals(event)) { - String[] otherValue = context.getRequest().getParameterValues(otherName); - log.warn("The event name was specified by two request parameters: ", - StripesConstants.URL_KEY_EVENT_NAME, "=", event, " and ", otherName, - "=", Arrays.toString(otherValue), ". ", "As of Stripes 1.5, ", - StripesConstants.URL_KEY_EVENT_NAME, - " overrides all other request parameters."); - } - } - catch (StripesRuntimeException e) { - // Ignore this. It means there were too many event params, which is OK in this case. - } - } - + } + + /** + * Looks to see if there is extra path information beyond simply the url binding of the bean. If + * it does and the next /-separated part of the path matches one of the known event names for the + * bean, that event name will be returned, otherwise null. + * + * @param bean the ActionBean type bound to the request + * @param context the ActionBeanContect for the current request + * @return String the name of the event submitted, or null if none can be found + */ + protected String getEventNameFromPath( + Class extends ActionBean> bean, ActionBeanContext context) { + Map mappings = this.eventMappings.get(bean); + String path = HttpUtil.getRequestedPath(context.getRequest()); + UrlBinding prototype = getUrlBindingFactory().getBindingPrototype(path); + String binding = prototype == null ? null : prototype.getPath(); + + if (binding != null && path.length() != binding.length()) { + String extra = path.substring(binding.length() + 1); + int index = extra.indexOf("/"); + String event = extra.substring(0, (index != -1) ? index : extra.length()); + if (mappings.containsKey(event)) { return event; + } } - /** - * Uses the Maps constructed earlier to locate the Method which can handle the event. - * - * @param bean the subclass of ActionBean that is bound to the request. - * @param eventName the name of the event being handled - * @return a Method object representing the handling method. - * @throws StripesServletException thrown when no method handles the named event. - */ - public Method getHandler(Class extends ActionBean> bean, String eventName) - throws StripesServletException { - Map mappings = this.eventMappings.get(bean); - Method handler = mappings.get(eventName); - - // If we could not find a handler then we should blow up quickly - if (handler == null) { - throw new StripesServletException( - "Could not find handler method for event name [" + eventName + "] on class [" + - bean.getName() + "]. Known handler mappings are: " + mappings); - } - - return handler; + return null; + } + + /** + * Looks to see if there is a single non-empty parameter value for the parameter name specified by + * {@link StripesConstants#URL_KEY_EVENT_NAME}. If there is, and it matches a known event it is + * returned, otherwise returns null. + * + * @param bean the ActionBean type bound to the request + * @param context the ActionBeanContect for the current request + * @return String the name of the event submitted, or null if none can be found + */ + protected String getEventNameFromEventNameParam( + Class extends ActionBean> bean, ActionBeanContext context) { + String[] values = context.getRequest().getParameterValues(StripesConstants.URL_KEY_EVENT_NAME); + String event = null; + if (values != null + && values.length == 1 + && this.eventMappings.get(bean).containsKey(values[0])) { + event = values[0]; } - /** - * Returns the Method that is the default handler for events in the ActionBean class supplied. - * If only one handler method is defined in the class, that is assumed to be the default. If - * there is more than one then the method marked with @DefaultHandler will be returned. - * - * @param bean the ActionBean type bound to the request - * @return Method object that should handle the request - * @throws StripesServletException if no default handler could be located - */ - public Method getDefaultHandler(Class extends ActionBean> bean) throws StripesServletException { - Map handlers = this.eventMappings.get(bean); - - if (handlers.size() == 1) { - return handlers.values().iterator().next(); - } - else { - Method handler = handlers.get(DEFAULT_HANDLER_KEY); - if (handler != null) return handler; + // Warn of non-backward-compatible behavior + if (event != null) { + try { + String otherName = getEventNameFromRequestParams(bean, context); + if (otherName != null && !otherName.equals(event)) { + String[] otherValue = context.getRequest().getParameterValues(otherName); + log.warn( + "The event name was specified by two request parameters: ", + StripesConstants.URL_KEY_EVENT_NAME, + "=", + event, + " and ", + otherName, + "=", + Arrays.toString(otherValue), + ". ", + "As of Stripes 1.5, ", + StripesConstants.URL_KEY_EVENT_NAME, + " overrides all other request parameters."); } - - // If we get this far, there is no sensible default! Kaboom! - throw new StripesServletException("No default handler could be found for ActionBean of " + - "type: " + bean.getName()); + } catch (StripesRuntimeException e) { + // Ignore this. It means there were too many event params, which is OK in this case. + } } - /** Provides subclasses with access to the configuration object. */ - protected Configuration getConfiguration() { return this.configuration; } - - /** - * Helper method to find implementations of ActionBean in the packages specified in - * Configuration using the {@link ResolverUtil} class. - * - * @return a set of Class objects that represent subclasses of ActionBean - */ - protected Set > findClasses() { - BootstrapPropertyResolver bootstrap = getConfiguration().getBootstrapPropertyResolver(); - - String packages = bootstrap.getProperty(PACKAGES); - if (packages == null) { - throw new StripesRuntimeException( - "You must supply a value for the configuration parameter '" + PACKAGES + "'. The " + - "value should be a list of one or more package roots (comma separated) that are " + - "to be scanned for ActionBean implementations. The packages specified and all " + - "subpackages are examined for implementations of ActionBean." - ); - } - - String[] pkgs = StringUtil.standardSplit(packages); - ResolverUtil resolver = new ResolverUtil (); - resolver.findImplementations(ActionBean.class, pkgs); - return resolver.getClasses(); + return event; + } + + /** + * Uses the Maps constructed earlier to locate the Method which can handle the event. + * + * @param bean the subclass of ActionBean that is bound to the request. + * @param eventName the name of the event being handled + * @return a Method object representing the handling method. + * @throws StripesServletException thrown when no method handles the named event. + */ + public Method getHandler(Class extends ActionBean> bean, String eventName) + throws StripesServletException { + Map mappings = this.eventMappings.get(bean); + Method handler = mappings.get(eventName); + + // If we could not find a handler then we should blow up quickly + if (handler == null) { + throw new StripesServletException( + "Could not find handler method for event name [" + + eventName + + "] on class [" + + bean.getName() + + "]. Known handler mappings are: " + + mappings); } - /** - * Get all the classes implementing {@link ActionBean} that are recognized by this - * {@link ActionResolver}. - */ - public Collection > getActionBeanClasses() { - return getUrlBindingFactory().getActionBeanClasses(); + return handler; + } + + /** + * Returns the Method that is the default handler for events in the ActionBean class supplied. If + * only one handler method is defined in the class, that is assumed to be the default. If there is + * more than one then the method marked with @DefaultHandler will be returned. + * + * @param bean the ActionBean type bound to the request + * @return Method object that should handle the request + * @throws StripesServletException if no default handler could be located + */ + public Method getDefaultHandler(Class extends ActionBean> bean) throws StripesServletException { + Map handlers = this.eventMappings.get(bean); + + if (handlers.size() == 1) { + return handlers.values().iterator().next(); + } else { + Method handler = handlers.get(DEFAULT_HANDLER_KEY); + if (handler != null) return handler; } - public Class extends ActionBean> getActionBeanByName(String actionBeanName) { - return actionBeansByName.get(actionBeanName); + // If we get this far, there is no sensible default! Kaboom! + throw new StripesServletException( + "No default handler could be found for ActionBean of " + "type: " + bean.getName()); + } + + /** Provides subclasses with access to the configuration object. */ + protected Configuration getConfiguration() { + return this.configuration; + } + + /** + * Helper method to find implementations of ActionBean in the packages specified in Configuration + * using the {@link ResolverUtil} class. + * + * @return a set of Class objects that represent subclasses of ActionBean + */ + protected Set > findClasses() { + BootstrapPropertyResolver bootstrap = getConfiguration().getBootstrapPropertyResolver(); + + String packages = bootstrap.getProperty(PACKAGES); + if (packages == null) { + throw new StripesRuntimeException( + "You must supply a value for the configuration parameter '" + + PACKAGES + + "'. The " + + "value should be a list of one or more package roots (comma separated) that are " + + "to be scanned for ActionBean implementations. The packages specified and all " + + "subpackages are examined for implementations of ActionBean."); } -} \ No newline at end of file + + String[] pkgs = StringUtil.standardSplit(packages); + ResolverUtil resolver = new ResolverUtil (); + resolver.findImplementations(ActionBean.class, pkgs); + return resolver.getClasses(); + } + + /** + * Get all the classes implementing {@link ActionBean} that are recognized by this {@link + * ActionResolver}. + */ + public Collection > getActionBeanClasses() { + return getUrlBindingFactory().getActionBeanClasses(); + } + + public Class extends ActionBean> getActionBeanByName(String actionBeanName) { + return actionBeansByName.get(actionBeanName); + } +} diff --git a/stripes/src/main/java/net/sourceforge/stripes/controller/BeforeAfterMethodInterceptor.java b/stripes/src/main/java/net/sourceforge/stripes/controller/BeforeAfterMethodInterceptor.java index 3151810e7..c2d999634 100755 --- a/stripes/src/main/java/net/sourceforge/stripes/controller/BeforeAfterMethodInterceptor.java +++ b/stripes/src/main/java/net/sourceforge/stripes/controller/BeforeAfterMethodInterceptor.java @@ -14,35 +14,34 @@ */ package net.sourceforge.stripes.controller; -import net.sourceforge.stripes.action.ActionBean; -import net.sourceforge.stripes.action.After; -import net.sourceforge.stripes.action.Before; -import net.sourceforge.stripes.action.Resolution; -import net.sourceforge.stripes.action.ActionBeanContext; -import net.sourceforge.stripes.util.Log; -import net.sourceforge.stripes.util.ReflectUtil; -import net.sourceforge.stripes.util.CollectionUtil; - import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; +import net.sourceforge.stripes.action.ActionBean; +import net.sourceforge.stripes.action.ActionBeanContext; +import net.sourceforge.stripes.action.After; +import net.sourceforge.stripes.action.Before; +import net.sourceforge.stripes.action.Resolution; +import net.sourceforge.stripes.util.CollectionUtil; +import net.sourceforge.stripes.util.Log; +import net.sourceforge.stripes.util.ReflectUtil; /** - * Interceptor that inspects ActionBeans for {@link Before} and {@link After} annotations and - * runs the annotated methods at the requested point in the request lifecycle. There is no limit - * on the number of methods within an ActionBean that can be marked with {@code @Before} and - * {@code @After} annotations, and individual methods may be marked with one or both annotations.
+ * Interceptor that inspects ActionBeans for {@link Before} and {@link After} annotations and runs + * the annotated methods at the requested point in the request lifecycle. There is no limit on the + * number of methods within an ActionBean that can be marked with {@code @Before} and {@code @After} + * annotations, and individual methods may be marked with one or both annotations. * *To configure the BeforeAfterMethodInterceptor for use you will need to add the following to - * your {@code web.xml} (assuming no other interceptors are yet configured):
+ * your {@code web.xml} (assuming no other interceptors are yet configured): * ** <init-param> @@ -51,283 +50,317 @@ * </init-param> ** - *If one or more interceptors are already configured in your {@code web.xml} simply separate - * the fully qualified names of the interceptors with commas (additional whitespace is ok).
- * + *If one or more interceptors are already configured in your {@code web.xml} simply separate the + * fully qualified names of the interceptors with commas (additional whitespace is ok). + * * @see net.sourceforge.stripes.action.Before * @see net.sourceforge.stripes.action.After * @author Jeppe Cramon * @since Stripes 1.3 */ -@Intercepts({LifecycleStage.RequestInit, - LifecycleStage.ActionBeanResolution, - LifecycleStage.HandlerResolution, - LifecycleStage.BindingAndValidation, - LifecycleStage.CustomValidation, - LifecycleStage.EventHandling, - LifecycleStage.ResolutionExecution, - LifecycleStage.RequestComplete}) +@Intercepts({ + LifecycleStage.RequestInit, + LifecycleStage.ActionBeanResolution, + LifecycleStage.HandlerResolution, + LifecycleStage.BindingAndValidation, + LifecycleStage.CustomValidation, + LifecycleStage.EventHandling, + LifecycleStage.ResolutionExecution, + LifecycleStage.RequestComplete +}) public class BeforeAfterMethodInterceptor implements Interceptor { - /** Log used throughout the intercetor */ - private static final Log log = Log.getInstance(BeforeAfterMethodInterceptor.class); + /** Log used throughout the intercetor */ + private static final Log log = Log.getInstance(BeforeAfterMethodInterceptor.class); - /** Cache of the FilterMethods for the different ActionBean classes */ - private Map
, FilterMethods> filterMethodsCache = - new ConcurrentHashMap , FilterMethods>(); + /** Cache of the FilterMethods for the different ActionBean classes */ + private Map , FilterMethods> filterMethodsCache = + new ConcurrentHashMap , FilterMethods>(); - /** - * Does the main work of the interceptor as described in the class level javadoc. - * Executed the before and after methods for the ActionBean as appropriate for the - * current lifecycle stage. Lazily examines the ActionBean to determine the set - * of methods to execute, if it has not yet been examined. - * - * @param context the current ExecutionContext - * @return a resolution if one of the Before or After methods returns one, or if the - * nested interceptors return one - * @throws Exception if one of the before/after methods raises an exception - */ - public Resolution intercept(ExecutionContext context) throws Exception { - LifecycleStage stage = context.getLifecycleStage(); - ActionBeanContext abc = context.getActionBeanContext(); - String event = abc == null ? null : abc.getEventName(); - Resolution resolution = null; + /** + * Does the main work of the interceptor as described in the class level javadoc. Executed the + * before and after methods for the ActionBean as appropriate for the current lifecycle stage. + * Lazily examines the ActionBean to determine the set of methods to execute, if it has not yet + * been examined. + * + * @param context the current ExecutionContext + * @return a resolution if one of the Before or After methods returns one, or if the nested + * interceptors return one + * @throws Exception if one of the before/after methods raises an exception + */ + public Resolution intercept(ExecutionContext context) throws Exception { + LifecycleStage stage = context.getLifecycleStage(); + ActionBeanContext abc = context.getActionBeanContext(); + String event = abc == null ? null : abc.getEventName(); + Resolution resolution = null; - // Run @Before methods, as long as there's a bean to run them on - if (context.getActionBean() != null) { - ActionBean bean = context.getActionBean(); - FilterMethods filterMethods = getFilterMethods(bean.getClass()); - List beforeMethods = filterMethods.getBeforeMethods(stage); + // Run @Before methods, as long as there's a bean to run them on + if (context.getActionBean() != null) { + ActionBean bean = context.getActionBean(); + FilterMethods filterMethods = getFilterMethods(bean.getClass()); + List beforeMethods = filterMethods.getBeforeMethods(stage); - for (Method method : beforeMethods) { - String[] on = method.getAnnotation(Before.class).on(); - if (event == null || CollectionUtil.applies(on, event)) { - resolution = invoke(bean, method, stage, Before.class); - if (resolution != null) { - return resolution; - } - } - } + for (Method method : beforeMethods) { + String[] on = method.getAnnotation(Before.class).on(); + if (event == null || CollectionUtil.applies(on, event)) { + resolution = invoke(bean, method, stage, Before.class); + if (resolution != null) { + return resolution; + } } + } + } - // Continue on and execute other filters and the lifecycle code - resolution = context.proceed(); + // Continue on and execute other filters and the lifecycle code + resolution = context.proceed(); - // Run After filter methods (if any) - if (context.getActionBean() != null) { - ActionBean bean = context.getActionBean(); - FilterMethods filterMethods = getFilterMethods(bean.getClass()); - List afterMethods = filterMethods.getAfterMethods(stage); + // Run After filter methods (if any) + if (context.getActionBean() != null) { + ActionBean bean = context.getActionBean(); + FilterMethods filterMethods = getFilterMethods(bean.getClass()); + List afterMethods = filterMethods.getAfterMethods(stage); - // Re-get the event name in case we're executing after handler resolution - // in which case the name will have been null before, and non-null now - event = abc == null ? null : abc.getEventName(); + // Re-get the event name in case we're executing after handler resolution + // in which case the name will have been null before, and non-null now + event = abc == null ? null : abc.getEventName(); - Resolution overrideResolution = null; - for (Method method : afterMethods) { - String[] on = method.getAnnotation(After.class).on(); - if (event == null || CollectionUtil.applies(on, event)) { - overrideResolution = invoke(bean, method, stage, After.class); - if (overrideResolution != null) { - return overrideResolution; - } - } - } + Resolution overrideResolution = null; + for (Method method : afterMethods) { + String[] on = method.getAnnotation(After.class).on(); + if (event == null || CollectionUtil.applies(on, event)) { + overrideResolution = invoke(bean, method, stage, After.class); + if (overrideResolution != null) { + return overrideResolution; + } } - - return resolution; - } + } + } - /** - * Helper method that will invoke the supplied method and manage any exceptions and - * returns from the object. Specifically it will log any exceptions except for - * InvocationTargetExceptions which it will attempt to unwrap and rethrow. If the method - * returns a Resolution it will be returned; returns of other types will be ignored. - */ - protected Resolution invoke(ActionBean bean, Method m, LifecycleStage stage, - Class extends Annotation> when) throws Exception { - Class extends ActionBean> beanClass = bean.getClass(); - Object retval = null; + return resolution; + } - log.debug("Calling @", when.getSimpleName(), " method '", m.getName(), "' at LifecycleStage '", - stage, "' on ActionBean '", beanClass.getSimpleName(), "'"); - try { - retval = m.invoke(bean); - } - catch (IllegalArgumentException e) { - log.error(e, "An InvalidArgumentException was raised when calling @", - when.getSimpleName(), " method '", m.getName(), "' at LifecycleStage '", - stage, "' on ActionBean '", beanClass.getSimpleName(), - "'. See java.lang.reflect.Method.invoke() for possible reasons."); - } - catch (IllegalAccessException e) { - log.error(e, "An IllegalAccessException was raised when calling @", - when.getSimpleName(), " method '", m.getName(), "' at LifecycleStage '", - stage, "' on ActionBean '", beanClass.getSimpleName(), "'"); - } - catch (InvocationTargetException e) { - // Method threw an exception, so throw the real cause of it - if (e.getCause() != null && e.getCause() instanceof Exception) { - throw (Exception)e.getCause(); - } - else { - throw e; - } - } + /** + * Helper method that will invoke the supplied method and manage any exceptions and returns from + * the object. Specifically it will log any exceptions except for InvocationTargetExceptions which + * it will attempt to unwrap and rethrow. If the method returns a Resolution it will be returned; + * returns of other types will be ignored. + */ + protected Resolution invoke( + ActionBean bean, Method m, LifecycleStage stage, Class extends Annotation> when) + throws Exception { + Class extends ActionBean> beanClass = bean.getClass(); + Object retval = null; - // If we got a return value and it is a resolution, return it - if (retval != null && retval instanceof Resolution) { - return (Resolution) retval; - } - else { - return null; - } + log.debug( + "Calling @", + when.getSimpleName(), + " method '", + m.getName(), + "' at LifecycleStage '", + stage, + "' on ActionBean '", + beanClass.getSimpleName(), + "'"); + try { + retval = m.invoke(bean); + } catch (IllegalArgumentException e) { + log.error( + e, + "An InvalidArgumentException was raised when calling @", + when.getSimpleName(), + " method '", + m.getName(), + "' at LifecycleStage '", + stage, + "' on ActionBean '", + beanClass.getSimpleName(), + "'. See java.lang.reflect.Method.invoke() for possible reasons."); + } catch (IllegalAccessException e) { + log.error( + e, + "An IllegalAccessException was raised when calling @", + when.getSimpleName(), + " method '", + m.getName(), + "' at LifecycleStage '", + stage, + "' on ActionBean '", + beanClass.getSimpleName(), + "'"); + } catch (InvocationTargetException e) { + // Method threw an exception, so throw the real cause of it + if (e.getCause() != null && e.getCause() instanceof Exception) { + throw (Exception) e.getCause(); + } else { + throw e; + } + } + // If we got a return value and it is a resolution, return it + if (retval != null && retval instanceof Resolution) { + return (Resolution) retval; + } else { + return null; } + } - /** - * Gets the Before/After methods for the ActionBean. Lazily examines the ActionBean - * and stores the information in a cache. Looks for all non-abstract, no-arg methods - * that are annotated with either {@code @Before} or {@code @After}. - * - * @param beanClass The action bean class to get methods for. - * @return The before and after methods for the ActionBean - */ - protected FilterMethods getFilterMethods(Class extends ActionBean> beanClass) { - FilterMethods filterMethods = filterMethodsCache.get(beanClass); - if (filterMethods == null) { - filterMethods = new FilterMethods(); + /** + * Gets the Before/After methods for the ActionBean. Lazily examines the ActionBean and stores the + * information in a cache. Looks for all non-abstract, no-arg methods that are annotated with + * either {@code @Before} or {@code @After}. + * + * @param beanClass The action bean class to get methods for. + * @return The before and after methods for the ActionBean + */ + protected FilterMethods getFilterMethods(Class extends ActionBean> beanClass) { + FilterMethods filterMethods = filterMethodsCache.get(beanClass); + if (filterMethods == null) { + filterMethods = new FilterMethods(); - // Look for @Before and @After annotations on the methods in the ActionBean class - Collection methods = ReflectUtil.getMethods(beanClass); - for (Method method : methods) { - if (method.isAnnotationPresent(Before.class) || method.isAnnotationPresent(After.class)) { - // Check to ensure that the method has an appropriate signature - int mods = method.getModifiers(); - if (method.getParameterTypes().length != 0 || Modifier.isAbstract(mods)) { - log.warn("Method '", beanClass.getName(), ".", method.getName(), "' is ", - "annotated with @Before or @After but has an incompatible ", - "signature. @Before/@After methods must be non-abstract ", - "zero-argument methods."); - continue; - } + // Look for @Before and @After annotations on the methods in the ActionBean class + Collection methods = ReflectUtil.getMethods(beanClass); + for (Method method : methods) { + if (method.isAnnotationPresent(Before.class) || method.isAnnotationPresent(After.class)) { + // Check to ensure that the method has an appropriate signature + int mods = method.getModifiers(); + if (method.getParameterTypes().length != 0 || Modifier.isAbstract(mods)) { + log.warn( + "Method '", + beanClass.getName(), + ".", + method.getName(), + "' is ", + "annotated with @Before or @After but has an incompatible ", + "signature. @Before/@After methods must be non-abstract ", + "zero-argument methods."); + continue; + } - // Now try and make private/protected/package methods callable - if (!method.isAccessible()) { - try { - method.setAccessible(true); - } - catch (SecurityException se) { - log.warn("Method '", beanClass.getName(), ".", method.getName(), "' is ", - "annotated with @Before or @After but is not public and ", - "calling setAccessible(true) on it threw a SecurityException. ", - "Please either declare the method as public, or change your ", - "JVM security policy to allow Stripes code to call ", - "Method.setAccessible() on your code base."); - continue; - } - } + // Now try and make private/protected/package methods callable + if (!method.isAccessible()) { + try { + method.setAccessible(true); + } catch (SecurityException se) { + log.warn( + "Method '", + beanClass.getName(), + ".", + method.getName(), + "' is ", + "annotated with @Before or @After but is not public and ", + "calling setAccessible(true) on it threw a SecurityException. ", + "Please either declare the method as public, or change your ", + "JVM security policy to allow Stripes code to call ", + "Method.setAccessible() on your code base."); + continue; + } + } - if (method.isAnnotationPresent(Before.class)) { - Before annotation = method.getAnnotation(Before.class); - filterMethods.addBeforeMethod(annotation.stages(), method); - } + if (method.isAnnotationPresent(Before.class)) { + Before annotation = method.getAnnotation(Before.class); + filterMethods.addBeforeMethod(annotation.stages(), method); + } - if (method.isAnnotationPresent(After.class)) { - After annotation = method.getAnnotation(After.class); - filterMethods.addAfterMethod(annotation.stages(), method); - } - } - } + if (method.isAnnotationPresent(After.class)) { + After annotation = method.getAnnotation(After.class); + filterMethods.addAfterMethod(annotation.stages(), method); + } + } + } - filterMethodsCache.put(beanClass, filterMethods); - } + filterMethodsCache.put(beanClass, filterMethods); + } - return filterMethods; - } - - /** - * Helper class used to collect Before and After methods for a class and provide easy - * and rapid access to them by LifecycleStage. - * - * @author Jeppe Cramon - */ - protected static class FilterMethods { - /** Map of Before methods, keyed by the LifecycleStage that they should be invoked before. */ - private Map > beforeMethods = new HashMap >(); + return filterMethods; + } - /** Map of After methods, keyed by the LifecycleStage that they should be invoked after. */ - private Map > afterMethods = new HashMap >(); - - /** - * Adds a method to be executed before the supplied LifecycleStages. - * - * @param stages All the LifecycleStages that the given filter method should be invoked before - * @param method The filter method to be invoked before the given LifecycleStage(s) - */ - public void addBeforeMethod(LifecycleStage[] stages, Method method) { - for (LifecycleStage stage : stages) { - if (stage == LifecycleStage.ActionBeanResolution) { - log.warn("LifecycleStage.ActionBeanResolution is unsupported for @Before ", - "methods. Method '", method.getDeclaringClass().getName(), ".", - method.getName(), "' will not be invoked for this stage."); - } - else { - addFilterMethod(beforeMethods, stage, method); - } - } - } - - /** - * Adds a method to be executed after the supplied LifecycleStages. - * - * @param stages All the LifecycleStages that the given filter method should be invoked after - * @param method The filter method to be invoked after the given LifecycleStage(s) - */ - public void addAfterMethod(LifecycleStage[] stages, Method method) { - for (LifecycleStage stage : stages) { - addFilterMethod(afterMethods, stage, method); - } - } + /** + * Helper class used to collect Before and After methods for a class and provide easy and rapid + * access to them by LifecycleStage. + * + * @author Jeppe Cramon + */ + protected static class FilterMethods { + /** Map of Before methods, keyed by the LifecycleStage that they should be invoked before. */ + private Map > beforeMethods = + new HashMap >(); - /** - * Helper method to add methods to a method map keyed by the LifecycleStage. - * - * @param methodMap The map of methods - * @param stage The LifecycleStage under which to put the method - * @param method The method that should be added to the method map - */ - private void addFilterMethod(Map > methodMap, - LifecycleStage stage, Method method) { - List methods = methodMap.get(stage); - if (methods == null) { - methods = new ArrayList (); - methodMap.put(stage, methods); - } - methods.add(method); - } - - /** - * Gets the Before methods for the given LifecycleStage. - * - * @param stage The LifecycleStage to find Before methods for. - * @return A List of before methods, possibly zero length but never null - */ - public List getBeforeMethods(LifecycleStage stage) { - List methods = beforeMethods.get(stage); - if (methods == null) methods = Collections.emptyList(); - return methods; + /** Map of After methods, keyed by the LifecycleStage that they should be invoked after. */ + private Map > afterMethods = + new HashMap >(); + + /** + * Adds a method to be executed before the supplied LifecycleStages. + * + * @param stages All the LifecycleStages that the given filter method should be invoked before + * @param method The filter method to be invoked before the given LifecycleStage(s) + */ + public void addBeforeMethod(LifecycleStage[] stages, Method method) { + for (LifecycleStage stage : stages) { + if (stage == LifecycleStage.ActionBeanResolution) { + log.warn( + "LifecycleStage.ActionBeanResolution is unsupported for @Before ", + "methods. Method '", + method.getDeclaringClass().getName(), + ".", + method.getName(), + "' will not be invoked for this stage."); + } else { + addFilterMethod(beforeMethods, stage, method); } + } + } + + /** + * Adds a method to be executed after the supplied LifecycleStages. + * + * @param stages All the LifecycleStages that the given filter method should be invoked after + * @param method The filter method to be invoked after the given LifecycleStage(s) + */ + public void addAfterMethod(LifecycleStage[] stages, Method method) { + for (LifecycleStage stage : stages) { + addFilterMethod(afterMethods, stage, method); + } + } + + /** + * Helper method to add methods to a method map keyed by the LifecycleStage. + * + * @param methodMap The map of methods + * @param stage The LifecycleStage under which to put the method + * @param method The method that should be added to the method map + */ + private void addFilterMethod( + Map > methodMap, LifecycleStage stage, Method method) { + List methods = methodMap.get(stage); + if (methods == null) { + methods = new ArrayList (); + methodMap.put(stage, methods); + } + methods.add(method); + } - /** - * Gets the Before methods for the given LifecycleStage. - * - * @param stage The LifecycleStage to find Before methods for. - * @return A List of before methods, possibly zero length but never null - */ - public List getAfterMethods(LifecycleStage stage) { - List methods = afterMethods.get(stage); - if (methods == null) methods = Collections.emptyList(); - return methods; - } - } + /** + * Gets the Before methods for the given LifecycleStage. + * + * @param stage The LifecycleStage to find Before methods for. + * @return A List of before methods, possibly zero length but never null + */ + public List getBeforeMethods(LifecycleStage stage) { + List methods = beforeMethods.get(stage); + if (methods == null) methods = Collections.emptyList(); + return methods; + } + + /** + * Gets the Before methods for the given LifecycleStage. + * + * @param stage The LifecycleStage to find Before methods for. + * @return A List of before methods, possibly zero length but never null + */ + public List getAfterMethods(LifecycleStage stage) { + List methods = afterMethods.get(stage); + if (methods == null) methods = Collections.emptyList(); + return methods; + } + } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/controller/BindingPolicyManager.java b/stripes/src/main/java/net/sourceforge/stripes/controller/BindingPolicyManager.java index 9d3ab526d..537b5e027 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/controller/BindingPolicyManager.java +++ b/stripes/src/main/java/net/sourceforge/stripes/controller/BindingPolicyManager.java @@ -14,6 +14,9 @@ */ package net.sourceforge.stripes.controller; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpSession; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; @@ -24,11 +27,6 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; - -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpSession; - import net.sourceforge.stripes.action.ActionBean; import net.sourceforge.stripes.action.ActionBeanContext; import net.sourceforge.stripes.action.StrictBinding; @@ -43,281 +41,283 @@ /** * Manages the policies observed by {@link DefaultActionBeanPropertyBinder} when binding properties * to an {@link ActionBean}. - * + * * @author Ben Gunter * @see StrictBinding */ @StrictBinding(defaultPolicy = Policy.ALLOW) public class BindingPolicyManager { - /** List of classes that, for security reasons, are not allowed as a {@link NodeEvaluation} value type. */ - private static final List > ILLEGAL_NODE_VALUE_TYPES = Arrays. > asList( - ActionBeanContext.class, - Class.class, - ClassLoader.class, - HttpSession.class, - ServletRequest.class, - ServletResponse.class); - - /** The regular expression that a property name must match */ - private static final String PROPERTY_REGEX = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; - - /** The compiled form of {@link #PROPERTY_REGEX} */ - private static final Pattern PROPERTY_PATTERN = Pattern.compile(PROPERTY_REGEX); - - /** Log */ - private static final Log log = Log.getInstance(BindingPolicyManager.class); - - /** Cached instances */ - private static final Map , BindingPolicyManager> instances = new HashMap , BindingPolicyManager>(); - - /** - * Get the policy manager for the given class. Instances are cached and returned on subsequent - * calls. - * - * @param beanType the class whose policy manager is to be retrieved - * @return a policy manager - */ - public static BindingPolicyManager getInstance(Class> beanType) { - if (instances.containsKey(beanType)) - return instances.get(beanType); - - BindingPolicyManager instance = new BindingPolicyManager(beanType); - instances.put(beanType, instance); - return instance; + /** + * List of classes that, for security reasons, are not allowed as a {@link NodeEvaluation} value + * type. + */ + private static final List > ILLEGAL_NODE_VALUE_TYPES = + Arrays. >asList( + ActionBeanContext.class, + Class.class, + ClassLoader.class, + HttpSession.class, + ServletRequest.class, + ServletResponse.class); + + /** The regular expression that a property name must match */ + private static final String PROPERTY_REGEX = + "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; + + /** The compiled form of {@link #PROPERTY_REGEX} */ + private static final Pattern PROPERTY_PATTERN = Pattern.compile(PROPERTY_REGEX); + + /** Log */ + private static final Log log = Log.getInstance(BindingPolicyManager.class); + + /** Cached instances */ + private static final Map , BindingPolicyManager> instances = + new HashMap , BindingPolicyManager>(); + + /** + * Get the policy manager for the given class. Instances are cached and returned on subsequent + * calls. + * + * @param beanType the class whose policy manager is to be retrieved + * @return a policy manager + */ + public static BindingPolicyManager getInstance(Class> beanType) { + if (instances.containsKey(beanType)) return instances.get(beanType); + + BindingPolicyManager instance = new BindingPolicyManager(beanType); + instances.put(beanType, instance); + return instance; + } + + /** The class to which the binding policy applies */ + private Class> beanClass; + + /** The default policy to honor, in case of conflicts */ + private Policy defaultPolicy; + + /** The regular expression that allowed properties must match */ + private Pattern allowPattern; + + /** The regular expression that denied properties must match */ + private Pattern denyPattern; + + /** The regular expression that matches properties with {@literal @Validate} */ + private Pattern validatePattern; + + /** + * Create a new instance to handle binding security for the given type. + * + * @param beanClass the class to which the binding policy applies + */ + protected BindingPolicyManager(Class> beanClass) { + try { + log.debug( + "Creating ", + getClass().getName(), + " for ", + beanClass, + " with default policy ", + defaultPolicy); + this.beanClass = beanClass; + + // process the annotation + StrictBinding annotation = getAnnotation(beanClass); + if (annotation != null) { + // set default policy + this.defaultPolicy = annotation.defaultPolicy(); + + // construct the allow pattern + this.allowPattern = globToPattern(annotation.allow()); + + // construct the deny pattern + this.denyPattern = globToPattern(annotation.deny()); + + // construct the validated properties pattern + this.validatePattern = globToPattern(getValidatedProperties(beanClass)); + } + } catch (Exception e) { + log.error(e, "%%% Failure instantiating ", getClass().getName()); + StripesRuntimeException sre = new StripesRuntimeException(e.getMessage(), e); + sre.setStackTrace(e.getStackTrace()); + throw sre; } - - /** The class to which the binding policy applies */ - private Class> beanClass; - - /** The default policy to honor, in case of conflicts */ - private Policy defaultPolicy; - - /** The regular expression that allowed properties must match */ - private Pattern allowPattern; - - /** The regular expression that denied properties must match */ - private Pattern denyPattern; - - /** The regular expression that matches properties with {@literal @Validate} */ - private Pattern validatePattern; - - /** - * Create a new instance to handle binding security for the given type. - * - * @param beanClass the class to which the binding policy applies - */ - protected BindingPolicyManager(Class> beanClass) { - try { - log.debug("Creating ", getClass().getName(), " for ", beanClass, - " with default policy ", defaultPolicy); - this.beanClass = beanClass; - - // process the annotation - StrictBinding annotation = getAnnotation(beanClass); - if (annotation != null) { - // set default policy - this.defaultPolicy = annotation.defaultPolicy(); - - // construct the allow pattern - this.allowPattern = globToPattern(annotation.allow()); - - // construct the deny pattern - this.denyPattern = globToPattern(annotation.deny()); - - // construct the validated properties pattern - this.validatePattern = globToPattern(getValidatedProperties(beanClass)); - } - } - catch (Exception e) { - log.error(e, "%%% Failure instantiating ", getClass().getName()); - StripesRuntimeException sre = new StripesRuntimeException(e.getMessage(), e); - sre.setStackTrace(e.getStackTrace()); - throw sre; - } + } + + /** + * Indicates if binding is allowed for the given expression. + * + * @param eval a property expression that has been evaluated against an {@link ActionBean} + * @return true if binding is allowed; false if not + */ + public boolean isBindingAllowed(PropertyExpressionEvaluation eval) { + // Ensure no-one is trying to bind into a protected type + if (usesIllegalNodeValueType(eval)) { + return false; } - /** - * Indicates if binding is allowed for the given expression. - * - * @param eval a property expression that has been evaluated against an {@link ActionBean} - * @return true if binding is allowed; false if not - */ - public boolean isBindingAllowed(PropertyExpressionEvaluation eval) { - // Ensure no-one is trying to bind into a protected type - if (usesIllegalNodeValueType(eval)) { - return false; - } - - // check parameter name against access lists - String paramName = new ParameterName(eval.getExpression().getSource()).getStrippedName(); - boolean deny = denyPattern != null && denyPattern.matcher(paramName).matches(); - boolean allow = (allowPattern != null && allowPattern.matcher(paramName).matches()) - || (validatePattern != null && validatePattern.matcher(paramName).matches()); - - /* - * if path appears on neither or both lists ( i.e. !(allow ^ deny) ) and default policy is - * to deny access, then fail - */ - if (defaultPolicy == Policy.DENY && !(allow ^ deny)) - return false; - - /* - * regardless of default policy, if it's in the deny list but not in the allow list, then - * fail - */ - if (!allow && deny) - return false; - - // any other conditions pass the test - return true; - } + // check parameter name against access lists + String paramName = new ParameterName(eval.getExpression().getSource()).getStrippedName(); + boolean deny = denyPattern != null && denyPattern.matcher(paramName).matches(); + boolean allow = + (allowPattern != null && allowPattern.matcher(paramName).matches()) + || (validatePattern != null && validatePattern.matcher(paramName).matches()); - /** - * Indicates if any node in the given {@link PropertyExpressionEvaluation} has a value type that is assignable from - * any of the classes listed in {@link #ILLEGAL_NODE_VALUE_TYPES}. - * - * @param eval a property expression that has been evaluated against an {@link ActionBean} - * @return true if the expression uses an illegal node value type; false otherwise + /* + * if path appears on neither or both lists ( i.e. !(allow ^ deny) ) and default policy is + * to deny access, then fail */ - protected boolean usesIllegalNodeValueType(PropertyExpressionEvaluation eval) { - for (NodeEvaluation node = eval.getRootNode(); node != null; node = node.getNext()) { - Type type = node.getValueType(); - if (type instanceof ParameterizedType) { - type = ((ParameterizedType) type).getRawType(); - } - if (type instanceof Class) { - final Class> nodeClass = (Class>) type; - for (Class> protectedClass : ILLEGAL_NODE_VALUE_TYPES) { - if (protectedClass.isAssignableFrom(nodeClass)) { - return true; - } - } - } - } - return false; - } + if (defaultPolicy == Policy.DENY && !(allow ^ deny)) return false; - /** - * Get the {@link StrictBinding} annotation for a class, checking all its superclasses if - * necessary. If no annotation is found, then one will be returned whose default policy is to - * allow binding to all properties. - * - * @param beanType the class to get the {@link StrictBinding} annotation for - * @return An annotation. This method never returns null. + /* + * regardless of default policy, if it's in the deny list but not in the allow list, then + * fail */ - protected StrictBinding getAnnotation(Class> beanType) { - StrictBinding annotation; - do { - annotation = beanType.getAnnotation(StrictBinding.class); - } while (annotation == null && (beanType = beanType.getSuperclass()) != null); - if (annotation == null) { - annotation = getClass().getAnnotation(StrictBinding.class); + if (!allow && deny) return false; + + // any other conditions pass the test + return true; + } + + /** + * Indicates if any node in the given {@link PropertyExpressionEvaluation} has a value type that + * is assignable from any of the classes listed in {@link #ILLEGAL_NODE_VALUE_TYPES}. + * + * @param eval a property expression that has been evaluated against an {@link ActionBean} + * @return true if the expression uses an illegal node value type; false otherwise + */ + protected boolean usesIllegalNodeValueType(PropertyExpressionEvaluation eval) { + for (NodeEvaluation node = eval.getRootNode(); node != null; node = node.getNext()) { + Type type = node.getValueType(); + if (type instanceof ParameterizedType) { + type = ((ParameterizedType) type).getRawType(); + } + if (type instanceof Class) { + final Class> nodeClass = (Class>) type; + for (Class> protectedClass : ILLEGAL_NODE_VALUE_TYPES) { + if (protectedClass.isAssignableFrom(nodeClass)) { + return true; + } } - return annotation; + } } - - /** - * Get all the properties and nested properties of the given class for which there is a - * corresponding {@link ValidationMetadata}, as returned by - * {@link ValidationMetadataProvider#getValidationMetadata(Class, ParameterName)}. The idea - * here is that if the bean property must be validated, then it is expected that the property - * may be bound to the bean. - * - * @param beanClass a class - * @return The validated properties. If no properties are annotated then null. - * @see ValidationMetadataProvider#getValidationMetadata(Class) - */ - protected String[] getValidatedProperties(Class> beanClass) { - Set properties = StripesFilter.getConfiguration().getValidationMetadataProvider() - .getValidationMetadata(beanClass).keySet(); - return new ArrayList (properties).toArray(new String[properties.size()]); + return false; + } + + /** + * Get the {@link StrictBinding} annotation for a class, checking all its superclasses if + * necessary. If no annotation is found, then one will be returned whose default policy is to + * allow binding to all properties. + * + * @param beanType the class to get the {@link StrictBinding} annotation for + * @return An annotation. This method never returns null. + */ + protected StrictBinding getAnnotation(Class> beanType) { + StrictBinding annotation; + do { + annotation = beanType.getAnnotation(StrictBinding.class); + } while (annotation == null && (beanType = beanType.getSuperclass()) != null); + if (annotation == null) { + annotation = getClass().getAnnotation(StrictBinding.class); } - - /** - * Get the bean class. - * - * @return the bean class - */ - public Class> getBeanClass() { - return beanClass; + return annotation; + } + + /** + * Get all the properties and nested properties of the given class for which there is a + * corresponding {@link ValidationMetadata}, as returned by {@link + * ValidationMetadataProvider#getValidationMetadata(Class, ParameterName)}. The idea here is that + * if the bean property must be validated, then it is expected that the property may be bound to + * the bean. + * + * @param beanClass a class + * @return The validated properties. If no properties are annotated then null. + * @see ValidationMetadataProvider#getValidationMetadata(Class) + */ + protected String[] getValidatedProperties(Class> beanClass) { + Set properties = + StripesFilter.getConfiguration() + .getValidationMetadataProvider() + .getValidationMetadata(beanClass) + .keySet(); + return new ArrayList (properties).toArray(new String[properties.size()]); + } + + /** + * Get the bean class. + * + * @return the bean class + */ + public Class> getBeanClass() { + return beanClass; + } + + /** + * Get the default policy. + * + * @return the policy + */ + public Policy getDefaultPolicy() { + return defaultPolicy; + } + + /** + * Converts a glob to a regex {@link Pattern}. + * + * @param globArray an array of property name globs, each of which may be a comma separated list + * of globs + * @return the pattern + */ + protected Pattern globToPattern(String... globArray) { + if (globArray == null || globArray.length == 0) return null; + + // things are much easier if we convert to a single list + List globs = new ArrayList (); + for (String glob : globArray) { + String[] subs = glob.split("(\\s*,\\s*)+"); + for (String sub : subs) { + globs.add(sub); + } } - /** - * Get the default policy. - * - * @return the policy - */ - public Policy getDefaultPolicy() { - return defaultPolicy; - } - - /** - * Converts a glob to a regex {@link Pattern}. - * - * @param globArray an array of property name globs, each of which may be a comma separated list - * of globs - * @return the pattern - */ - protected Pattern globToPattern(String... globArray) { - if (globArray == null || globArray.length == 0) + List subs = new ArrayList (); + StringBuilder buf = new StringBuilder(); + for (String glob : globs) { + buf.setLength(0); + String[] properties = glob.split("\\."); + for (int i = 0; i < properties.length; i++) { + String property = properties[i]; + if ("*".equals(property)) { + buf.append(PROPERTY_REGEX); + } else if ("**".equals(property)) { + buf.append(PROPERTY_REGEX).append("(\\.").append(PROPERTY_REGEX).append(")*"); + } else if (property.length() > 0) { + Matcher matcher = PROPERTY_PATTERN.matcher(property); + if (matcher.matches()) { + buf.append(property); + } else { + log.warn("Invalid property name: " + property); return null; - - // things are much easier if we convert to a single list - List globs = new ArrayList (); - for (String glob : globArray) { - String[] subs = glob.split("(\\s*,\\s*)+"); - for (String sub : subs) { - globs.add(sub); - } + } } - List subs = new ArrayList (); - StringBuilder buf = new StringBuilder(); - for (String glob : globs) { - buf.setLength(0); - String[] properties = glob.split("\\."); - for (int i = 0; i < properties.length; i++) { - String property = properties[i]; - if ("*".equals(property)) { - buf.append(PROPERTY_REGEX); - } - else if ("**".equals(property)) { - buf.append(PROPERTY_REGEX).append("(\\.").append(PROPERTY_REGEX).append(")*"); - } - else if (property.length() > 0) { - Matcher matcher = PROPERTY_PATTERN.matcher(property); - if (matcher.matches()) { - buf.append(property); - } - else { - log.warn("Invalid property name: " + property); - return null; - } - } - - // add a literal dot after all but the last - if (i < properties.length - 1) - buf.append("\\."); - } - - // add to the list of subs - if (buf.length() != 0) - subs.add(buf.toString()); - } + // add a literal dot after all but the last + if (i < properties.length - 1) buf.append("\\."); + } - // join subs together with pipes and compile - buf.setLength(0); - for (String sub : subs) { - buf.append(sub).append('|'); - } - if (buf.length() > 0) - buf.setLength(buf.length() - 1); - log.debug("Translated globs ", Arrays.toString(globArray), " to regex ", buf); + // add to the list of subs + if (buf.length() != 0) subs.add(buf.toString()); + } - // return null if pattern is empty - if (buf.length() == 0) - return null; - else - return Pattern.compile(buf.toString()); + // join subs together with pipes and compile + buf.setLength(0); + for (String sub : subs) { + buf.append(sub).append('|'); } + if (buf.length() > 0) buf.setLength(buf.length() - 1); + log.debug("Translated globs ", Arrays.toString(globArray), " to regex ", buf); + + // return null if pattern is empty + if (buf.length() == 0) return null; + else return Pattern.compile(buf.toString()); + } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/controller/DefaultActionBeanContextFactory.java b/stripes/src/main/java/net/sourceforge/stripes/controller/DefaultActionBeanContextFactory.java index 040d7db31..b828d533a 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/controller/DefaultActionBeanContextFactory.java +++ b/stripes/src/main/java/net/sourceforge/stripes/controller/DefaultActionBeanContextFactory.java @@ -14,75 +14,76 @@ */ package net.sourceforge.stripes.controller; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import net.sourceforge.stripes.action.ActionBeanContext; import net.sourceforge.stripes.config.Configuration; import net.sourceforge.stripes.exception.StripesServletException; import net.sourceforge.stripes.util.Log; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - /** * Implements an ActionBeanContextFactory that allows for instantiation of application specific * ActionBeanContext classes. Looks for a configuration parameters called "ActionBeanContext.Class". * If the property is present, the named class with be instantiated and returned from the - * getContextInstance() method. If no class is named, then the default class, ActionBeanContext - * will be instantiated. + * getContextInstance() method. If no class is named, then the default class, ActionBeanContext will + * be instantiated. * * @author Tim Fennell */ public class DefaultActionBeanContextFactory implements ActionBeanContextFactory { - private static final Log log = Log.getInstance(DefaultActionBeanContextFactory.class); + private static final Log log = Log.getInstance(DefaultActionBeanContextFactory.class); - /** The name of the configuration property used for the context class name. */ - public static final String CONTEXT_CLASS_NAME = "ActionBeanContext.Class"; + /** The name of the configuration property used for the context class name. */ + public static final String CONTEXT_CLASS_NAME = "ActionBeanContext.Class"; - private Configuration configuration; - private Class extends ActionBeanContext> contextClass; + private Configuration configuration; + private Class extends ActionBeanContext> contextClass; - /** Stores the configuration, and looks up the ActionBeanContext class specified. */ - public void init(Configuration configuration) throws Exception { - setConfiguration(configuration); + /** Stores the configuration, and looks up the ActionBeanContext class specified. */ + public void init(Configuration configuration) throws Exception { + setConfiguration(configuration); - Class extends ActionBeanContext> clazz = configuration.getBootstrapPropertyResolver() - .getClassProperty(CONTEXT_CLASS_NAME, ActionBeanContext.class); - if (clazz == null) { - clazz = ActionBeanContext.class; - } - else { - log.info(DefaultActionBeanContextFactory.class.getSimpleName(), " will use ", - ActionBeanContext.class.getSimpleName(), " subclass ", clazz.getName()); - } - this.contextClass = clazz; + Class extends ActionBeanContext> clazz = + configuration + .getBootstrapPropertyResolver() + .getClassProperty(CONTEXT_CLASS_NAME, ActionBeanContext.class); + if (clazz == null) { + clazz = ActionBeanContext.class; + } else { + log.info( + DefaultActionBeanContextFactory.class.getSimpleName(), + " will use ", + ActionBeanContext.class.getSimpleName(), + " subclass ", + clazz.getName()); } + this.contextClass = clazz; + } - /** - * Returns a new instance of the configured class, or ActionBeanContext if a class is - * not specified. - */ - public ActionBeanContext getContextInstance(HttpServletRequest request, - HttpServletResponse response) throws ServletException { - try { - ActionBeanContext context = getConfiguration().getObjectFactory().newInstance( - this.contextClass); - context.setRequest(request); - context.setResponse(response); - return context; - } - catch (Exception e) { - throw new StripesServletException("Could not instantiate configured " + - "ActionBeanContext class: " + this.contextClass, e); - } + /** + * Returns a new instance of the configured class, or ActionBeanContext if a class is not + * specified. + */ + public ActionBeanContext getContextInstance( + HttpServletRequest request, HttpServletResponse response) throws ServletException { + try { + ActionBeanContext context = + getConfiguration().getObjectFactory().newInstance(this.contextClass); + context.setRequest(request); + context.setResponse(response); + return context; + } catch (Exception e) { + throw new StripesServletException( + "Could not instantiate configured " + "ActionBeanContext class: " + this.contextClass, e); } + } - protected Configuration getConfiguration() - { - return configuration; - } + protected Configuration getConfiguration() { + return configuration; + } - protected void setConfiguration(Configuration configuration) - { - this.configuration = configuration; - } + protected void setConfiguration(Configuration configuration) { + this.configuration = configuration; + } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java b/stripes/src/main/java/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java index 78305402b..48cfd645d 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java +++ b/stripes/src/main/java/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java @@ -14,6 +14,21 @@ */ package net.sourceforge.stripes.controller; +import jakarta.servlet.http.HttpServletRequest; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; import net.sourceforge.stripes.action.ActionBean; import net.sourceforge.stripes.action.ActionBeanContext; import net.sourceforge.stripes.action.FileBean; @@ -38,851 +53,855 @@ import net.sourceforge.stripes.validation.ValidationMetadata; import net.sourceforge.stripes.validation.expression.ExpressionValidator; -import javax.servlet.http.HttpServletRequest; -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; - /** - * * Implementation of the ActionBeanPropertyBinder interface that uses Stripes' built in property * expression support to perform JavaBean property binding. Several additions/enhancements are * available above and beyond the standard JavaBean syntax. These include: - *
- * + * *- *
- * + * * @author Tim Fennell * @since Stripes 1.4 */ public class DefaultActionBeanPropertyBinder implements ActionBeanPropertyBinder { - private static final Log log = Log.getInstance(DefaultActionBeanPropertyBinder.class); - - /** Configuration instance passed in at initialization time. */ - private Configuration configuration; - - /** - * Looks up and caches in a useful form the metadata necessary to perform validations as - * properties are bound to the bean. - */ - public void init(Configuration configuration) throws Exception { - this.configuration = configuration; + private static final Log log = Log.getInstance(DefaultActionBeanPropertyBinder.class); + + /** Configuration instance passed in at initialization time. */ + private Configuration configuration; + + /** + * Looks up and caches in a useful form the metadata necessary to perform validations as + * properties are bound to the bean. + */ + public void init(Configuration configuration) throws Exception { + this.configuration = configuration; + } + + /** Returns the Configuration object that was passed to the init() method. */ + protected Configuration getConfiguration() { + return configuration; + } + + /** + * Loops through the parameters contained in the request and attempts to bind each one to the + * supplied ActionBean. Invokes validation for each of the properties on the bean before binding + * is attempted. Only fields which do not produce validation errors will be bound to the + * ActionBean. + * + *- The ability to instantiate and set null intermediate properties in a property chain
- *- The ability to use Lists and Maps directly for indexed properties
- *- The ability to infer type information from the generics information in classes
+ *- The ability to instantiate and set null intermediate properties in a property chain + *
- The ability to use Lists and Maps directly for indexed properties + *
- The ability to infer type information from the generics information in classes *
Individual property binding is delegated to the other interface method, bind(ActionBean, + * String, Object), in order to allow for easy extension of this class. + * + * @param bean the ActionBean whose properties are to be validated and bound + * @param context the ActionBeanContext of the current request + * @param validate true indicates that validation should be run, false indicates that only type + * conversion should occur + */ + public ValidationErrors bind(ActionBean bean, ActionBeanContext context, boolean validate) { + ValidationErrors fieldErrors = context.getValidationErrors(); + Map
validationInfos = + this.configuration.getValidationMetadataProvider().getValidationMetadata(bean.getClass()); + + // Take the ParameterMap and turn the keys into ParameterNames + Map parameters = getParameters(bean); + + // Run the required validation first to catch fields that weren't even submitted + if (validate) { + validateRequiredFields(parameters, bean, fieldErrors); } - /** Returns the Configuration object that was passed to the init() method. */ - protected Configuration getConfiguration() { return configuration; } - - /** - * - * Loops through the parameters contained in the request and attempts to bind each one to the - * supplied ActionBean. Invokes validation for each of the properties on the bean before binding - * is attempted. Only fields which do not produce validation errors will be bound to the - * ActionBean. - *
- * - *- * Individual property binding is delegated to the other interface method, bind(ActionBean, - * String, Object), in order to allow for easy extension of this class. - *
- * - * @param bean the ActionBean whose properties are to be validated and bound - * @param context the ActionBeanContext of the current request - * @param validate true indicates that validation should be run, false indicates that only type - * conversion should occur - */ - public ValidationErrors bind(ActionBean bean, ActionBeanContext context, boolean validate) { - ValidationErrors fieldErrors = context.getValidationErrors(); - MapvalidationInfos = this.configuration - .getValidationMetadataProvider().getValidationMetadata(bean.getClass()); - - // Take the ParameterMap and turn the keys into ParameterNames - Map parameters = getParameters(bean); - - // Run the required validation first to catch fields that weren't even submitted - if (validate) { - validateRequiredFields(parameters, bean, fieldErrors); - } - - // Converted values for all fields are accumulated in this map to make post-conversion - // validation go a little easier - Map > allConvertedFields = new TreeMap >(); - - // First we bind all the regular parameters - for (Map.Entry entry : parameters.entrySet()) { - List