1 /* 2 * $Id$ 3 * 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 */ 21 22 package org.apache.struts.extras.actions; 23 24 import java.lang.reflect.Method; 25 import java.util.StringTokenizer; 26 27 import jakarta.servlet.ServletException; 28 import jakarta.servlet.http.HttpServletRequest; 29 import jakarta.servlet.http.HttpServletResponse; 30 31 import org.apache.struts.action.Action; 32 import org.apache.struts.action.ActionForm; 33 import org.apache.struts.action.ActionForward; 34 import org.apache.struts.action.ActionMapping; 35 import org.slf4j.Logger; 36 import org.slf4j.LoggerFactory; 37 38 /** 39 * <p>An Action helper class that dispatches to to one of the public methods 40 * that are named in the <code>parameter</code> attribute of the corresponding 41 * ActionMapping and matches a submission parameter. This is useful for 42 * developers who prefer to use many submit buttons, images, or submit links 43 * on a single form and whose related actions exist in a single Action class.</p> 44 * 45 * <p>The method(s) in the associated <code>Action</code> must have the same 46 * signature (other than method name) of the standard Action.execute method.</p> 47 * 48 * <p>To configure the use of this action in your 49 * <code>struts-config.xml</code> file, create an entry like this:</p> 50 * 51 * <pre><code> 52 * <action path="/saveSubscription" 53 * type="org.example.SubscriptionAction" 54 * name="subscriptionForm" 55 * scope="request" 56 * input="/subscription.jsp" 57 * parameter="save,back,recalc=recalculate,default=save"/> 58 * </code></pre> 59 * 60 * <p>where <code>parameter</code> contains three possible methods and one 61 * default method if nothing matches (such as the user pressing the enter key).</p> 62 * 63 * <p>For utility purposes, you can use the <code>key=value</code> notation to 64 * alias methods so that they are exposed as different form element names, in the 65 * event of a naming conflict or otherwise. In this example, the <em>recalc</em> 66 * button (via a request parameter) will invoke the <code>recalculate</code> 67 * method. The security-minded person may find this feature valuable to 68 * obfuscate and not expose the methods.</p> 69 * 70 * <p>The <em>default</em> key is purely optional. If this is not specified 71 * and no parameters match the list of method keys, <code>null</code> is 72 * returned which means the <code>unspecified</code> method will be invoked.</p> 73 * 74 * <p>The order of the parameters are guaranteed to be iterated in the order 75 * specified. If multiple buttons were accidently submitted, the first match in 76 * the list will be dispatched.</p> 77 * 78 * <p>To implement this <i>dispatch</i> behaviour in an <code>Action</code>, 79 * class create your custom Action as follows, along with the methods you require 80 * (and optionally "cancelled" and "unspecified" methods):</p> <p/> 81 * <pre> 82 * public class MyCustomAction extends Action { 83 * 84 * protected ActionDispatcher dispatcher = new EventActionDispatcher(this); 85 * 86 * public ActionForward execute(ActionMapping mapping, 87 * ActionForm form, 88 * HttpServletRequest request, 89 * HttpServletResponse response) 90 * throws Exception { 91 * return dispatcher.execute(mapping, form, request, response); 92 * } 93 * } 94 * </pre> 95 * <p/> 96 * 97 * @since Struts 1.2.9 98 */ 99 public class EventActionDispatcher extends ActionDispatcher { 100 private static final long serialVersionUID = 9211507807543933033L; 101 102 /** 103 * The {@code Log} instance for this class. 104 */ 105 private transient final Logger log = 106 LoggerFactory.getLogger(EventActionDispatcher.class); 107 108 /** 109 * The method key, if present, to use if other specified method keys 110 * do not match a request parameter. 111 */ 112 private static final String DEFAULT_METHOD_KEY = "default"; 113 114 /** 115 * Constructs a new object for the specified action. 116 * @param action the action 117 */ 118 public EventActionDispatcher(Action action) { 119 // N.B. MAPPING_FLAVOR causes the getParameter() method 120 // in ActionDispatcher to throw an exception if the 121 // parameter is missing 122 super(action, ActionDispatcher.MAPPING_FLAVOR); 123 } 124 125 /** 126 * <p>Dispatches to the target class' <code>unspecified</code> method, if 127 * present, otherwise throws a ServletException. Classes utilizing 128 * <code>EventActionDispatcher</code> should provide an <code>unspecified</code> 129 * method if they wish to provide behavior different than throwing a 130 * ServletException.</p> 131 * 132 * @param mapping The ActionMapping used to select this instance 133 * @param form The optional ActionForm bean for this request (if any) 134 * @param request The non-HTTP request we are processing 135 * @param response The non-HTTP response we are creating 136 * @return The forward to which control should be transferred, or 137 * <code>null</code> if the response has been completed. 138 * @throws Exception if the application business logic throws an 139 * exception. 140 */ 141 protected ActionForward unspecified(ActionMapping mapping, ActionForm form, 142 HttpServletRequest request, HttpServletResponse response) 143 throws Exception { 144 // Identify if there is an "unspecified" method to be dispatched to 145 String name = "unspecified"; 146 Method method = null; 147 148 try { 149 method = getMethod(name); 150 } catch (NoSuchMethodException e) { 151 String message = 152 messages.getMessage("event.parameter", mapping.getPath()); 153 154 log.error("{} {}", message, mapping.getParameter()); 155 156 throw new ServletException(message, e); 157 } 158 159 return dispatchMethod(mapping, form, request, response, name, method); 160 } 161 162 /** 163 * Returns the method name, given a parameter's value. 164 * 165 * @param mapping The ActionMapping used to select this instance 166 * @param form The optional ActionForm bean for this request (if 167 * any) 168 * @param request The HTTP request we are processing 169 * @param response The HTTP response we are creating 170 * @param parameter The <code>ActionMapping</code> parameter's name 171 * @return The method's name. 172 * @throws Exception if an error occurs. 173 */ 174 protected String getMethodName(ActionMapping mapping, ActionForm form, 175 HttpServletRequest request, HttpServletResponse response, 176 String parameter) throws Exception { 177 178 StringTokenizer st = new StringTokenizer(parameter, ","); 179 String defaultMethodName = null; 180 181 while (st.hasMoreTokens()) { 182 String methodKey = st.nextToken().trim(); 183 String methodName = methodKey; 184 185 // The key can either be a direct method name or an alias 186 // to a method as indicated by a "key=value" signature 187 int equals = methodKey.indexOf('='); 188 if (equals > -1) { 189 methodName = methodKey.substring(equals + 1).trim(); 190 methodKey = methodKey.substring(0, equals).trim(); 191 } 192 193 // Set the default if it passes by 194 if (methodKey.equals(DEFAULT_METHOD_KEY)) { 195 defaultMethodName = methodName; 196 } 197 198 // If the method key exists as a standalone parameter or with 199 // the image suffixes (.x/.y), the method name has been found. 200 if ((request.getParameter(methodKey) != null) 201 || (request.getParameter(methodKey + ".x") != null)) { 202 return methodName; 203 } 204 } 205 206 return defaultMethodName; 207 } 208 }