FormTag.java

package sk.iway.iwcm.system.stripes;
/* Copyright 2005-2006 Tim Fennell
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;

import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.Wizard;
import net.sourceforge.stripes.controller.ActionResolver;
import net.sourceforge.stripes.controller.StripesConstants;
import net.sourceforge.stripes.controller.StripesFilter;
import net.sourceforge.stripes.exception.StripesJspException;
import net.sourceforge.stripes.tag.InputCheckBoxTag;
import net.sourceforge.stripes.tag.InputHiddenTag;
import net.sourceforge.stripes.tag.InputSelectTag;
import net.sourceforge.stripes.tag.InputTagSupport;
import net.sourceforge.stripes.tag.ParameterizableTag;
import net.sourceforge.stripes.tag.WizardFieldsTag;
import net.sourceforge.stripes.util.CryptoUtil;
import net.sourceforge.stripes.util.HtmlUtil;
import net.sourceforge.stripes.util.Log;
import net.sourceforge.stripes.util.UrlBuilder;
import net.sourceforge.stripes.validation.ValidationError;
import net.sourceforge.stripes.validation.ValidationErrors;

/**
* <p>Form tag for use with the Stripes framework.  Supports all of the HTML attributes applicable
* to the form tag, with one exception: due to JSP attribute naming restrictions accept-charset is
* specified as acceptcharset (but will be rendered correctly in the output HTML).</p>
*
* @author Tim Fennell
*/
public class FormTag extends net.sourceforge.stripes.tag.FormTag
{

	boolean actionAllreadySet = false;

   /** Log used to log error and debugging information for this class. */
   private static final Log log = Log.getInstance(FormTag.class);

   /** Stores the field name (or magic values ''/'first') to set focus on. */
   private String focus;
   private boolean focusSet = false;
   private boolean partial = false;
   private String enctype = null;
   private String method = null;

   /** Stores the value of the action attribute before the context gets appended. */
   private String actionWithoutContext;

   /** Stores the value of the beanclass attribute. */
   private Object beanclass;

   /**
    * The {@link ActionBean} class to which the form will submit, as determined by the
    * {@link ActionResolver}. This may be null if the action attribute is set but its value does
    * not resolve to an {@link ActionBean}.
    */
   private Class<? extends ActionBean> actionBeanClass;

   /** Builds the action attribute with parameters */
   private UrlBuilder urlBuilder;

   /** A map of field name to field type for all fields registered with the form. */
   private Map<String,Class<?>> fieldsPresent = new HashMap<String,Class<?>>();

   /**
    * Sets the action for the form.  If the form action begins with a slash, and does not
    * already contain the context path, then the context path of the web application will get
    * prepended to the action before it is set. In general actions should be specified as
    * &quot;absolute&quot; paths within the web application, therefore allowing them to function
    * correctly regardless of the address currently shown in the browser&apos;s address bar.
    *
    * @param action the action path, relative to the root of the web application
    */
   @Override
   public void setAction(String action)
   {
       //this.actionWithoutContext = action;	// takto vyzera originalna setAction
       if (action.endsWith(".htm") || action.endsWith(".html") || action.endsWith("/") || action.endsWith(".jsp") || action.endsWith(".do") || action.startsWith("/showdoc.do") || action.indexOf("__sfu=")!=-1 || action.indexOf(".html?")!=-1 || action.indexOf("/?")!=-1)
       {
      	 HttpServletResponse response = (HttpServletResponse) getPageContext().getResponse();
      	 StringBuilder actionString = new StringBuilder(response.encodeURL(action));
      	 int startIndex = actionString.indexOf(";jsessionid");
          if(startIndex != -1)
          {
         	 int endIndex = actionString.indexOf("?", startIndex);
        	  	 if(endIndex == -1) endIndex = actionString.length();
        	  	 actionString.replace(startIndex, endIndex, "");
          }
      	 set("action", actionString.toString());
      	 actionAllreadySet = true;
      	 return;
       }

       // Use the action resolver to figure out what the appropriate URL binding if for
       // this path and use that if there is one, otherwise just use the action passed in
       String binding = StripesFilter.getConfiguration().getActionResolver().getUrlBindingFromPath(action);
       if (binding != null) {
           this.actionWithoutContext = binding;
       }
       else {
           this.actionWithoutContext = action;
       }

       if (action.startsWith("/")) {
           HttpServletRequest request = (HttpServletRequest) getPageContext().getRequest();
           String contextPath = request.getContextPath();

           if (contextPath != null && !"/".equals(contextPath) && !action.contains(contextPath + "/")) {
               action = contextPath + action;
           }
       }

       if (!actionAllreadySet)
       {
	       HttpServletResponse response = (HttpServletResponse) getPageContext().getResponse();
	       set("action", response.encodeURL(action));
	       System.out.println("\n\n s response: " + response.encodeURL(action) +"\n\n");
       }

   }
   @Override
   public String getAction()
   {
   	return this.actionWithoutContext;
   }

   /** Get the URL binding for the form's {@link ActionBean} from the {@link ActionResolver}. */
   @Override
   protected String getActionBeanUrlBinding() {
       ActionResolver resolver = StripesFilter.getConfiguration().getActionResolver();
       if (actionBeanClass == null) {
           String binding = resolver.getUrlBindingFromPath(actionWithoutContext);
           if (binding == null)
               binding = actionWithoutContext;
           return binding;
       }
       else {
           return resolver.getUrlBinding(actionBeanClass);
       }
   }

   /** Lazily looks up and returns the type of action bean the form will submit to. */
   @Override
   protected Class<? extends ActionBean> getActionBeanClass() {
       if (this.actionBeanClass == null) {
           ActionResolver resolver = StripesFilter.getConfiguration().getActionResolver();
           this.actionBeanClass = resolver.getActionBeanType(getActionBeanUrlBinding());
       }

       return this.actionBeanClass;
   }

   /**
    * Sets the 'action' attribute by inspecting the bean class provided and asking the current
    * ActionResolver what the appropriate URL is.
    *
    * @param beanclass the String FQN of the class, or a Class representing the class
    * @throws StripesJspException if the URL cannot be determined for any reason, most likely
    *         because of a mis-spelled class name, or a class that's not an ActionBean
    */
   @Override
   public void setBeanclass(Object beanclass) throws StripesJspException {
       this.beanclass = beanclass;

       String url = getActionBeanUrl(beanclass);
       if (url == null) {
           throw new StripesJspException("Could not determine action from 'beanclass' supplied. " +
               "The value supplied was '" + beanclass + "'. Please ensure that this bean type " +
               "exists and is in the classpath. If you are developing a page and the ActionBean " +
               "does not yet exist, consider using the 'action' attribute instead for now.");
       }
       else {
           setAction(url);
       }
   }

   /** Corresponding getter for 'beanclass', will always return null. */
   @Override
   public Object getBeanclass() { return null; }

   /** Sets the name of the field that should receive focus when the form is rendered. */
   @Override
   public void setFocus(String focus) { this.focus = focus; }
   /** Gets the name of the field that should receive focus when the form is rendered. */
   @Override
   public String getFocus() { return focus; }

   /** Gets the flag that indicates if this is a partial form. */
   @Override
   public boolean isPartial() { return partial; }
   /** Sets the flag that indicates if this is a partial form. */
   @Override
   public void setPartial(boolean partial) { this.partial = partial; }

   /** Sets the form encoding. */
   @Override
   public void setEnctype(String enctype) { this.enctype = enctype; }
   /** Gets the form encoding. */
   @Override
   public String getEnctype() { return enctype; };

   /** Sets the HTTP method to use when the form is submitted. */
   @Override
   public void setMethod(String method) { this.method = method; }
   /** Gets the HTTP method to use when the form is submitted. */
   @Override
   public String getMethod() { return method; }

   ////////////////////////////////////////////////////////////
   // Additional attributes specific to the form tag
   ////////////////////////////////////////////////////////////
   @Override
   public void   setAccept(String accept) { set("accept", accept); }
   @Override
   public String getAccept() { return get("accept"); }
   @Override
   public void   setAcceptcharset(String acceptCharset) { set("accept-charset", acceptCharset); }
   @Override
   public String getAcceptcharset() { return get("accept-charset"); }
   @Override
   public void   setName(String name) { set("name", name); }
   @Override
   public String getName() { return get("name"); }
   @Override
   public void   setTarget(String target) { set("target", target); }
   @Override
   public String getTarget() { return get("target"); }
   @Override
   public void   setOnreset(String onreset) { set("onreset", onreset); }
   @Override
   public String getOnreset() { return get("onreset"); }
   @Override
   public void   setOnsubmit(String onsubmit) { set("onsubmit", onsubmit); }
   @Override
   public String getOnsubmit() { return get("onsubmit"); }

   ////////////////////////////////////////////////////////////
   // TAG methods
   ////////////////////////////////////////////////////////////

   /**
    * Does sanity checks and returns EVAL_BODY_BUFFERED. Everything else of interest happens in
    * doEndTag.
    */
   @Override
   public int doStartTag() throws JspException {
       if (this.actionWithoutContext == null) {
           throw new StripesJspException("The form tag attributes 'beanClass' and 'action' "
                   + "are both null. One of the two must be supplied to determine which "
                   + "action bean should handle the form submission.");
       }
       getTagStack().push(this);
       urlBuilder = new UrlBuilder(pageContext.getRequest().getLocale(), getAction(), false)
               .setEvent(null);
       return EVAL_BODY_BUFFERED;
   }

   /** No-op method. */
   @Override
   public void doInitBody() throws JspException { }

   /** Just returns SKIP_BODY so that the body is included only once. */
   @Override
   public int doAfterBody() throws JspException {
       return SKIP_BODY;
   }

   /**
    * Writes things out in the following order:
    * <ul>
    *   <li>The form open tag</li>
    *   <li>Hidden fields for the form name and source page</li>
    *   <li>The buffered body content</li>
    *   <li>The form close tag</li>
    * </ul>
    *
    * <p>All of this is done in doEndTag to allow form elements to modify the form tag itself if
    * necessary.  A prime example of this is the InputFileTag, which needs to ensure that the form
    * method is GET and the enctype is correct.</p>
    */
   @Override
   public int doEndTag() throws JspException {
       try {
           // Default the method to post
           if (getMethod() == null) {
               setMethod("get");
           }

           set("method", getMethod());
           set("enctype", getEnctype());
           //set("action", buildAction()); metoda je nepotrebna, kedze action sa vyskladava po nasom v setAction()

           JspWriter out = getPageContext().getOut();
           if (!isPartial()) {
               writeOpenTag(out, "form");
           }
           if (getBodyContent() != null) {
               getBodyContent().writeOut( getPageContext().getOut() );
           }

           if (!isPartial()) {
               // Write out a hidden field with the name of the page in it....
               // The div is necessary in order to be XHTML compliant, where a form can contain
               // only block level elements (which seems stupid, but whatever).
               out.write("<div style=\"display: none;\">");
               out.write("<input type=\"hidden\" name=\"");
               out.write(StripesConstants.URL_KEY_SOURCE_PAGE);
               out.write("\" value=\"");
               //HttpServletRequest request = (HttpServletRequest) getPageContext().getRequest();
               out.write(CryptoUtil.encrypt("/components/maybeError.jsp"));
               out.write("\" />");

               if (isWizard()) {
                   writeWizardFields();
               }

               writeFieldsPresentHiddenField(out);

               if ("post".equalsIgnoreCase(getMethod()))
               {
               	CSRF.writeCsrfTokenInputFiled(getPageContext().getSession(), out);
               }

               out.write("</div>");

               writeCloseTag(getPageContext().getOut(), "form");
           }

           // Write out a warning if focus didn't find a field
           if (this.focus != null && !this.focusSet) {
               log.error("Form with action [", getAction(), "] has 'focus' set to '", this.focus,
                         "', but did not find a field with matching name to set focus on.");
           }

           // Clean up any state that we've modified during tag processing, so that the container
           // can use tag pooling
           this.actionBeanClass = null;
           this.fieldsPresent.clear();
           this.focusSet = false;
           this.urlBuilder = null;
       }
       catch (IOException ioe) {
           throw new StripesJspException("IOException in FormTag.doEndTag().", ioe);
       }

       return EVAL_PAGE;
   }

   /** Rethrows the passed in throwable in all cases. */
   @Override
   public void doCatch(Throwable throwable) throws Throwable { throw throwable; }

   /**
    * Used to ensure that the form is always removed from the tag stack so that there is
    * never any confusion about tag-parent hierarchies.
    */
   @Override
   public void doFinally() {
       try { getTagStack().pop(); }
       catch (Throwable t) {
           /* Suppress anything, because otherwise this might mask any causal exception. */
       }
   }

   /**
    * <p>In general writes out a hidden field notifying the server exactly what fields were
    * present on the page.  Exact behaviour depends upon whether or not the current form
    * is a wizard or not. When the current form is <b>not</b> a wizard this method examines
    * the form tag to determine what fields present in the form might not get submitted to
    * the server (e.g. checkboxes, selects), writes out a hidden field that contains the names
    * of all those fields so that we can detect non-submission when the request comes back.</p>
    *
    * <p>In the case of a wizard form the value output is the full list of all fields that were
    * present on the page. This is done because the list is used to drive required field
    * validation knowing that in a wizard required fields may be spread across several pages.</p>
    *
    * <p>In both cases the value is encrypted to stop the user maliciously spoofing the value.</p>
    *
    * @param out the output  writer into which the hidden tag should be written
    * @throws IOException if the writer throws one
    */
   @Override
   protected void writeFieldsPresentHiddenField(JspWriter out) throws IOException {
       // Figure out what set of names to include
       Set<String> namesToInclude = new HashSet<String>();

       if (isWizard()) {
           namesToInclude.addAll(this.fieldsPresent.keySet());
       }
       else {
           for (Map.Entry<String,Class<?>> entry : this.fieldsPresent.entrySet()) {
               Class<?> fieldClass = entry.getValue();
               if (InputSelectTag.class.isAssignableFrom(fieldClass)
                       || InputCheckBoxTag.class.isAssignableFrom(fieldClass)) {
                   namesToInclude.add(entry.getKey());
               }
           }
       }

       // Combine the names into a delimited String and encrypt it
       String hiddenFieldValue = HtmlUtil.combineValues(namesToInclude);
       hiddenFieldValue = CryptoUtil.encrypt(hiddenFieldValue);

       out.write("<input type=\"hidden\" name=\"");
       out.write(StripesConstants.URL_KEY_FIELDS_PRESENT);
       out.write("\" value=\"");
       out.write(hiddenFieldValue);
       out.write("\" />");
   }

   /**
    * Fetches the ActionBean associated with the form if one is present.  An ActionBean will not
    * be created (and hence not present) by default.  An ActionBean will only be present if the
    * current request got bound to the same ActionBean as the current form uses.  E.g. if we are
    * re-showing the page as the result of an error, or the same ActionBean is used for a
    * &quot;pre-Action&quot; and the &quot;post-action&quot;.
    *
    * @return ActionBean the ActionBean bound to the form if there is one
    */
   @Override
   protected ActionBean getActionBean() {
		String binding = getActionBeanUrlBinding();
		HttpServletRequest request = (HttpServletRequest) getPageContext().getRequest();
		ActionBean bean = (ActionBean) request.getAttribute(binding);
		if (bean == null) {
			HttpSession session = request.getSession(false);
			if (session != null)
				bean = (ActionBean) session.getAttribute(binding);
		}
		return bean;
	}

   /**
    * Returns true if the ActionBean this form posts to represents a Wizard action bean and
    * false in all other situations.  If the form cannot determine the ActionBean being posted
    * to for any reason it will return false.
    */
   @Override
   protected boolean isWizard() {
       ActionBean bean = getActionBean();
       Class<? extends ActionBean> clazz = null;
       if (bean == null) {
           clazz = getActionBeanClass();

           if (clazz == null) {
               log.error("Could not locate an ActionBean that was bound to the URL [",
                         this.actionWithoutContext, "]. Without an ActionBean class Stripes ",
                         "cannot determine whether the ActionBean is a wizard or not. ",
                         "As a result wizard behaviour will be disabled.");
               return false;
           }
       }
       else {
           clazz = bean.getClass();
       }

       return clazz.getAnnotation(Wizard.class) != null;
   }

   /**
    * Writes out hidden fields for all fields that are present in the request but are not
    * explicitly present in this form.  Excludes any fields that have special meaning to
    * Stripes and are not really application data.  Uses the stripes:wizard-fields tag to
    * do the grunt work.
    */
   @Override
   protected void writeWizardFields() throws JspException {
       WizardFieldsTag tag = new WizardFieldsTag();
       tag.setPageContext(getPageContext());
       tag.setParent(this);
       try {
           tag.doStartTag();
           tag.doEndTag();
       }
       finally {
           tag.doFinally();
           tag.release();
       }
   }

   /**
    * Used by nested tags to notify the form that a field with the specified name has been
    * written to the form.
    *
    * @param tag the input field tag being registered
    */
   @Override
   public void registerField(InputTagSupport tag) {
       this.fieldsPresent.put(tag.getName(), tag.getClass());
       setFocusOnFieldIfRequired(tag);
   }

   /**
    * Checks to see if the field should receive focus either because it is the named
    * field for receiving focus, because it is the first field in the form (and first
    * field focus was specified), or because it is the first field in error.
    *
    * @param tag the input tag being registered with the form
    */
   @Override
   protected void setFocusOnFieldIfRequired(InputTagSupport tag) {
       // Decide whether or not this field should be focused
       if (this.focus != null && !this.focusSet) {
           ActionBean bean = getActionBean();
           ValidationErrors errors = bean == null ? null : bean.getContext().getValidationErrors();

           // If there are validation errors, select the first field in error
           if (errors != null && errors.hasFieldErrors()) {
               List<ValidationError> fieldErrors = errors.get(tag.getName());
               if (fieldErrors != null && fieldErrors.size() > 0) {
                   tag.setFocus(true);
                   this.focusSet = true;
               }
           }
           // Else set the named field, or the first field if that's desired
           else if (this.focus.equals(tag.getName())) {
                   tag.setFocus(true);
                   this.focusSet = true;
           }
           else if ("".equals(this.focus) || "first".equalsIgnoreCase(this.focus)) {
               if ( !(tag instanceof InputHiddenTag) ) {
                   tag.setFocus(true);
                   this.focusSet = true;
               }
           }
       }
   }

   /**
    * Gets the set of all field names for which fields have been referred within the form up
    * until the point of calling this method. If this is called during doEndTag it will contain
    * all field names, if it is called during the body of the tag it will only contain the
    * input elements which have been processed up until that point.
    *
    * @return Set<String> - the set of field names seen so far
    */
   @Override
   public Set<String> getRegisteredFields() {
       return this.fieldsPresent.keySet();
   }

   /**
    * Appends a parameter to the "action" attribute of the form tag. For clean URLs the value will
    * be embedded in the URL if possible. Otherwise, it will be added to the query string.
    *
    * @param name the parameter name
    * @param valueOrValues the parameter value(s)
    * @see ParameterizableTag#addParameter(String, Object)
    */
   @Override
   public void addParameter(String name, Object valueOrValues) {
       urlBuilder.addParameter(name, valueOrValues);
   }

   /**
    * Builds the action attribute, including the context path and any parameters.
    *
    * @return the action attribute
    */
   @Override
   protected String buildAction() {
       String action = urlBuilder.toString();
       if (action.startsWith("/")) {
           HttpServletRequest request = (HttpServletRequest) getPageContext().getRequest();
           String contextPath = request.getContextPath();

           // *Always* prepend the context path if "beanclass" was used
           // Otherwise, *only* prepend it if it is not already present
           if (contextPath.length() > 1
                   && (beanclass != null || !action.startsWith(contextPath + '/'))) {
               action = contextPath + action;
           }
       }
       HttpServletResponse response = (HttpServletResponse) getPageContext().getResponse();
       return response.encodeURL(action);
   }
}