/* 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.
*/
package net.sourceforge.stripes.action;
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;
/**
* <p>
* 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.</p>
*
* <p>
* 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.</p>
*
* @author Tim Fennell
*/
public abstract class OnwardResolution<T extends OnwardResolution<T>> 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<String, Object> parameters = new HashMap<String, Object>();
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));
}
/**
* 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.
*
* @return Event name specified within this resolution
*/
public String getEvent() {
return event == VALUE_NOT_SET ? null : event;
}
/**
* Return true if an event name was specified when this instance was
* constructed.
*
* @return Whether or not an event was specified for this resolution.
*/
public boolean isEventSpecified() {
return event != VALUE_NOT_SET;
}
/**
* Accessor for the path that the user should be sent to.
*
* @return Path that the user should be sent to by this resolution.
*/
public String getPath() {
return path;
}
/**
* Setter for the path that the user should be sent to.
*
* @param path - Path that the user should be sent to by this resolution.
*/
public void setPath(String path) {
this.path = path;
}
/**
* Get the name of the anchor to be appended to the URL.
*
* @return Name of the anchor to be appended to the URL by this resolution.
*/
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() + "'"
+ "}";
}
/**
* <p>
* 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.</p>
*
* <p>
* 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.</p>
*
* @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;
}
/**
* <p>
* 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.</p>
*
* <p>
* 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.</p>
*
* @param parameters a Map of parameters as described above
* @return this Resolution so that methods can be chained
*/
@SuppressWarnings("unchecked")
public T addParameters(Map<String, ? extends Object> parameters) {
for (Map.Entry<String, ? extends Object> entry : parameters.entrySet()) {
addParameter(entry.getKey(), entry.getValue());
}
return (T) this;
}
/**
* <p>
* 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.</p>
*
* @return the Map of parameters for the resolution
*/
public Map<String, Object> 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
* @return The locale-specific URL that this resolution points to.
*/
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();
}
}