001: /*
002: * $Id: Form.java 569304 2007-08-24 09:12:20Z nilsga $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts2.components;
022:
023: import com.opensymphony.xwork2.ActionContext;
024: import com.opensymphony.xwork2.ActionInvocation;
025: import com.opensymphony.xwork2.ObjectFactory;
026: import com.opensymphony.xwork2.config.Configuration;
027: import com.opensymphony.xwork2.config.RuntimeConfiguration;
028: import com.opensymphony.xwork2.config.entities.ActionConfig;
029: import com.opensymphony.xwork2.config.entities.InterceptorMapping;
030: import com.opensymphony.xwork2.inject.Inject;
031: import com.opensymphony.xwork2.interceptor.MethodFilterInterceptorUtil;
032: import com.opensymphony.xwork2.util.TextUtils;
033: import com.opensymphony.xwork2.util.ValueStack;
034: import com.opensymphony.xwork2.validator.ActionValidatorManagerFactory;
035: import com.opensymphony.xwork2.validator.FieldValidator;
036: import com.opensymphony.xwork2.validator.ValidationInterceptor;
037: import com.opensymphony.xwork2.validator.Validator;
038: import org.apache.commons.logging.Log;
039: import org.apache.commons.logging.LogFactory;
040: import org.apache.struts2.StrutsConstants;
041: import org.apache.struts2.dispatcher.Dispatcher;
042: import org.apache.struts2.dispatcher.mapper.ActionMapping;
043: import org.apache.struts2.portlet.context.PortletActionContext;
044: import org.apache.struts2.portlet.util.PortletUrlHelper;
045: import org.apache.struts2.views.annotations.StrutsTag;
046: import org.apache.struts2.views.annotations.StrutsTagAttribute;
047: import org.apache.struts2.views.util.UrlHelper;
048:
049: import javax.servlet.http.HttpServletRequest;
050: import javax.servlet.http.HttpServletResponse;
051: import java.util.ArrayList;
052: import java.util.Collections;
053: import java.util.List;
054: import java.util.Set;
055:
056: /**
057: * <!-- START SNIPPET: javadoc -->
058: * <p/>
059: * Renders HTML an input form.<p/>
060: * <p/>
061: * The remote form allows the form to be submitted without the page being refreshed. The results from the form
062: * can be inserted into any HTML element on the page.<p/>
063: * <p/>
064: * NOTE:<p/>
065: * The order / logic in determining the posting url of the generated HTML form is as follows:-
066: * <ol>
067: * <li>
068: * If the action attribute is not specified, then the current request will be used to
069: * determine the posting url
070: * </li>
071: * <li>
072: * If the action is given, Struts will try to obtain an ActionConfig. This will be
073: * successfull if the action attribute is a valid action alias defined struts.xml.
074: * </li>
075: * <li>
076: * If the action is given and is not an action alias defined in struts.xml, Struts
077: * will used the action attribute as if it is the posting url, separting the namespace
078: * from it and using UrlHelper to generate the final url.
079: * </li>
080: * </ol>
081: * <p/>
082: * <!-- END SNIPPET: javadoc -->
083: * <p/>
084: * <p/> <b>Examples</b>
085: * <p/>
086: * <pre>
087: * <!-- START SNIPPET: example -->
088: * <p/>
089: * <s:form ... />
090: * <p/>
091: * <!-- END SNIPPET: example -->
092: * </pre>
093: *
094: */
095: @StrutsTag(name="form",tldTagClass="org.apache.struts2.views.jsp.ui.FormTag",description="Renders an input form")
096: public class Form extends ClosingUIBean {
097: /**
098: * Provide a logging instance.
099: */
100: private static final Log LOG = LogFactory.getLog(Form.class);
101:
102: public static final String OPEN_TEMPLATE = "form";
103: public static final String TEMPLATE = "form-close";
104:
105: private int sequence = 0;
106:
107: protected String onsubmit;
108: protected String action;
109: protected String target;
110: protected String enctype;
111: protected String method;
112: protected String namespace;
113: protected String validate;
114: protected String portletMode;
115: protected String windowState;
116: protected String acceptcharset;
117:
118: protected boolean enableDynamicMethodInvocation = true;
119: protected Configuration configuration;
120: protected ObjectFactory objectFactory;
121:
122: public Form(ValueStack stack, HttpServletRequest request,
123: HttpServletResponse response) {
124: super (stack, request, response);
125: }
126:
127: protected boolean evaluateNameValue() {
128: return false;
129: }
130:
131: public String getDefaultOpenTemplate() {
132: return OPEN_TEMPLATE;
133: }
134:
135: protected String getDefaultTemplate() {
136: return TEMPLATE;
137: }
138:
139: @Inject(StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION)
140: public void setEnableDynamicMethodInvocation(String enable) {
141: enableDynamicMethodInvocation = "true".equals(enable);
142: }
143:
144: @Inject
145: public void setConfiguration(Configuration configuration) {
146: this .configuration = configuration;
147: }
148:
149: @Inject
150: public void setObjectFactory(ObjectFactory objectFactory) {
151: this .objectFactory = objectFactory;
152: }
153:
154: /*
155: * Revised for Portlet actionURL as form action, and add wwAction as hidden
156: * field. Refer to template.simple/form.vm
157: */
158: protected void evaluateExtraParams() {
159: super .evaluateExtraParams();
160:
161: //boolean isAjax = "ajax".equalsIgnoreCase(this.theme);
162:
163: if (validate != null) {
164: addParameter("validate", findValue(validate, Boolean.class));
165: }
166:
167: // calculate the action and namespace
168: /*String action = null;
169: if (this.action != null) {
170: // if it isn't specified, we'll make somethig up
171: action = findString(this.action);
172: }
173:
174: if (Dispatcher.getInstance().isPortletSupportActive() && PortletActionContext.isPortletRequest()) {
175: evaluateExtraParamsPortletRequest(namespace, action);
176: } else {
177: String namespace = determineNamespace(this.namespace, getStack(),
178: request);
179: evaluateExtraParamsServletRequest(action, namespace, isAjax);
180: }*/
181:
182: if (onsubmit != null) {
183: addParameter("onsubmit", findString(onsubmit));
184: }
185:
186: if (target != null) {
187: addParameter("target", findString(target));
188: }
189:
190: if (enctype != null) {
191: addParameter("enctype", findString(enctype));
192: }
193:
194: if (method != null) {
195: addParameter("method", findString(method));
196: }
197:
198: if (acceptcharset != null) {
199: addParameter("acceptcharset", findString(acceptcharset));
200: }
201:
202: // keep a collection of the tag names for anything special the templates might want to do (such as pure client
203: // side validation)
204: if (!parameters.containsKey("tagNames")) {
205: // we have this if check so we don't do this twice (on open and close of the template)
206: addParameter("tagNames", new ArrayList());
207: }
208: }
209:
210: /**
211: * The Form component determines its HTML element id as follows:-
212: * <ol>
213: * <li>if an 'id' attribute is specified.</li>
214: * <li>if an 'action' attribute is specified, it will be used as the id.</li>
215: * </ol>
216: */
217: protected void populateComponentHtmlId(Form form) {
218: boolean isAjax = "ajax".equalsIgnoreCase(this .theme);
219:
220: String action = null;
221: if (this .action != null) {
222: // if it isn't specified, we'll make somethig up
223: action = findString(this .action);
224: }
225:
226: if (id != null) {
227: addParameter("id", escape(id));
228: }
229:
230: // if no id given, it will be tried to generate it from the action attribute in the
231: // corresponding evaluateExtraParams method
232: if (Dispatcher.getInstance().isPortletSupportActive()
233: && PortletActionContext.isPortletRequest()) {
234: evaluateExtraParamsPortletRequest(namespace, action);
235: } else {
236: String namespace = determineNamespace(this .namespace,
237: getStack(), request);
238: evaluateExtraParamsServletRequest(action, namespace, isAjax);
239: }
240: }
241:
242: /**
243: * @param isAjax
244: * @param namespace
245: * @param action
246: */
247: private void evaluateExtraParamsServletRequest(String action,
248: String namespace, boolean isAjax) {
249: if (action == null) {
250: // no action supplied? ok, then default to the current request (action or general URL)
251: ActionInvocation ai = (ActionInvocation) getStack()
252: .getContext().get(ActionContext.ACTION_INVOCATION);
253: if (ai != null) {
254: action = ai.getProxy().getActionName();
255: namespace = ai.getProxy().getNamespace();
256: } else {
257: // hmm, ok, we need to just assume the current URL cut down
258: String uri = request.getRequestURI();
259: action = uri.substring(uri.lastIndexOf('/'));
260: }
261: }
262:
263: String actionMethod = "";
264: // FIXME: our implementation is flawed - the only concept of ! should be in DefaultActionMapper
265: // handle "name!method" convention.
266: if (enableDynamicMethodInvocation) {
267: if (action.indexOf("!") != -1) {
268: int endIdx = action.lastIndexOf("!");
269: actionMethod = action.substring(endIdx + 1, action
270: .length());
271: action = action.substring(0, endIdx);
272: }
273: }
274:
275: final ActionConfig actionConfig = configuration
276: .getRuntimeConfiguration().getActionConfig(namespace,
277: action);
278: String actionName = action;
279: if (actionConfig != null) {
280:
281: ActionMapping mapping = new ActionMapping(action,
282: namespace, actionMethod, parameters);
283: String result = UrlHelper.buildUrl(actionMapper
284: .getUriFromActionMapping(mapping), request,
285: response, null);
286: addParameter("action", result);
287:
288: // let's try to get the actual action class and name
289: // this can be used for getting the list of validators
290: addParameter("actionName", actionName);
291: try {
292: Class clazz = objectFactory
293: .getClassInstance(actionConfig.getClassName());
294: addParameter("actionClass", clazz);
295: } catch (ClassNotFoundException e) {
296: // this is OK, we'll just move on
297: }
298:
299: addParameter("namespace", namespace);
300:
301: // if the name isn't specified, use the action name
302: if (name == null) {
303: addParameter("name", action);
304: }
305:
306: // if the id isn't specified, use the action name
307: if (id == null && action != null) {
308: addParameter("id", escape(action));
309: }
310: } else if (action != null) {
311: // Since we can't find an action alias in the configuration, we just assume
312: // the action attribute supplied is the path to be used as the URI this
313: // form is submitting to.
314:
315: // Warn user that the specified namespace/action combo
316: // was not found in the configuration.
317: if (namespace != null) {
318: LOG
319: .warn("No configuration found for the specified action: '"
320: + action
321: + "' in namespace: '"
322: + namespace
323: + "'. Form action defaulting to 'action' attribute's literal value.");
324: }
325:
326: String result = UrlHelper.buildUrl(action, request,
327: response, null);
328: addParameter("action", result);
329:
330: // namespace: cut out anything between the start and the last /
331: int slash = result.lastIndexOf('/');
332: if (slash != -1) {
333: addParameter("namespace", result.substring(0, slash));
334: } else {
335: addParameter("namespace", "");
336: }
337:
338: // name/id: cut out anything between / and . should be the id and name
339: if (id == null) {
340: slash = result.lastIndexOf('/');
341: int dot = result.indexOf('.', slash);
342: if (dot != -1) {
343: id = result.substring(slash + 1, dot);
344: } else {
345: id = result.substring(slash + 1);
346: }
347: addParameter("id", escape(id));
348: }
349: }
350:
351: // WW-1284
352: // evaluate if client-side js is to be enabled. (if validation interceptor
353: // does allow validation eg. method is not filtered out)
354: evaluateClientSideJsEnablement(actionName, namespace,
355: actionMethod);
356: }
357:
358: private void evaluateClientSideJsEnablement(String actionName,
359: String namespace, String actionMethod) {
360:
361: // Only evaluate if Client-Side js is to be enable when validate=true
362: Boolean validate = (Boolean) getParameters().get("validate");
363: if (validate != null && validate) {
364:
365: addParameter("performValidation", Boolean.FALSE);
366:
367: RuntimeConfiguration runtimeConfiguration = configuration
368: .getRuntimeConfiguration();
369: ActionConfig actionConfig = runtimeConfiguration
370: .getActionConfig(namespace, actionName);
371:
372: if (actionConfig != null) {
373: List<InterceptorMapping> interceptors = actionConfig
374: .getInterceptors();
375: for (InterceptorMapping interceptorMapping : interceptors) {
376: if (ValidationInterceptor.class
377: .isInstance(interceptorMapping
378: .getInterceptor())) {
379: ValidationInterceptor validationInterceptor = (ValidationInterceptor) interceptorMapping
380: .getInterceptor();
381:
382: Set excludeMethods = validationInterceptor
383: .getExcludeMethodsSet();
384: Set includeMethods = validationInterceptor
385: .getIncludeMethodsSet();
386:
387: if (MethodFilterInterceptorUtil.applyMethod(
388: excludeMethods, includeMethods,
389: actionMethod)) {
390: addParameter("performValidation",
391: Boolean.TRUE);
392: }
393: return;
394: }
395: }
396: }
397: }
398: }
399:
400: /**
401: * Constructs the action url adapted to a portal environment.
402: *
403: * @param action The action to create the URL for.
404: */
405: private void evaluateExtraParamsPortletRequest(String namespace,
406: String action) {
407:
408: String type = "action";
409: if (TextUtils.stringSet(method)) {
410: if ("GET".equalsIgnoreCase(method.trim())) {
411: type = "render";
412: }
413: }
414: if (action != null) {
415: String result = PortletUrlHelper.buildUrl(action,
416: namespace, null, getParameters(), type,
417: portletMode, windowState);
418: addParameter("action", result);
419:
420: // namespace: cut out anything between the start and the last /
421: int slash = result.lastIndexOf('/');
422: if (slash != -1) {
423: addParameter("namespace", result.substring(0, slash));
424: } else {
425: addParameter("namespace", "");
426: }
427:
428: // name/id: cut out anything between / and . should be the id and
429: // name
430: if (id == null) {
431: slash = action.lastIndexOf('/');
432: int dot = action.indexOf('.', slash);
433: if (dot != -1) {
434: id = action.substring(slash + 1, dot);
435: } else {
436: id = action.substring(slash + 1);
437: }
438: addParameter("id", escape(id));
439: }
440: }
441:
442: }
443:
444: public List getValidators(String name) {
445: Class actionClass = (Class) getParameters().get("actionClass");
446: if (actionClass == null) {
447: return Collections.EMPTY_LIST;
448: }
449:
450: List<Validator> all = ActionValidatorManagerFactory
451: .getInstance().getValidators(actionClass,
452: (String) getParameters().get("actionName"));
453: List<Validator> validators = new ArrayList<Validator>();
454: for (Validator validator : all) {
455: if (validator instanceof FieldValidator) {
456: FieldValidator fieldValidator = (FieldValidator) validator;
457: if (fieldValidator.getFieldName().equals(name)) {
458: validators.add(fieldValidator);
459: }
460: }
461: }
462:
463: return validators;
464: }
465:
466: /**
467: * Get a incrementing sequence unique to this <code>Form</code> component.
468: * It is used by <code>Form</code> component's child that might need a
469: * sequence to make them unique.
470: *
471: * @return int
472: */
473: protected int getSequence() {
474: return sequence++;
475: }
476:
477: @StrutsTagAttribute(description="HTML onsubmit attribute")
478: public void setOnsubmit(String onsubmit) {
479: this .onsubmit = onsubmit;
480: }
481:
482: @StrutsTagAttribute(description="Set action name to submit to, without .action suffix",defaultValue="current action")
483: public void setAction(String action) {
484: this .action = action;
485: }
486:
487: @StrutsTagAttribute(description="HTML form target attribute")
488: public void setTarget(String target) {
489: this .target = target;
490: }
491:
492: @StrutsTagAttribute(description="HTML form enctype attribute")
493: public void setEnctype(String enctype) {
494: this .enctype = enctype;
495: }
496:
497: @StrutsTagAttribute(description="HTML form method attribute")
498: public void setMethod(String method) {
499: this .method = method;
500: }
501:
502: @StrutsTagAttribute(description="Namespace for action to submit to",defaultValue="current namespace")
503: public void setNamespace(String namespace) {
504: this .namespace = namespace;
505: }
506:
507: @StrutsTagAttribute(description="Whether client side/remote validation should be performed. Only" + " useful with theme xhtml/ajax",type="Boolean",defaultValue="false")
508: public void setValidate(String validate) {
509: this .validate = validate;
510: }
511:
512: @StrutsTagAttribute(description="The portlet mode to display after the form submit")
513: public void setPortletMode(String portletMode) {
514: this .portletMode = portletMode;
515: }
516:
517: @StrutsTagAttribute(description="The window state to display after the form submit")
518: public void setWindowState(String windowState) {
519: this .windowState = windowState;
520: }
521:
522: @StrutsTagAttribute(description="The accepted charsets for this form. The values may be comma or blank delimited.")
523: public void setAcceptcharset(String acceptcharset) {
524: this.acceptcharset = acceptcharset;
525: }
526: }
|