Package net.sourceforge.stripes.controller

Source Code of net.sourceforge.stripes.controller.AnnotatedClassActionResolver

/* 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.controller;

import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.DefaultHandler;
import net.sourceforge.stripes.action.HandlesEvent;
import net.sourceforge.stripes.action.SessionScope;
import net.sourceforge.stripes.config.BootstrapPropertyResolver;
import net.sourceforge.stripes.config.Configuration;
import net.sourceforge.stripes.config.DontAutoLoad;
import net.sourceforge.stripes.exception.ActionBeanNotFoundException;
import net.sourceforge.stripes.exception.StripesRuntimeException;
import net.sourceforge.stripes.exception.StripesServletException;
import net.sourceforge.stripes.util.HttpUtil;
import net.sourceforge.stripes.util.Log;
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.List;
import java.util.Map;
import java.util.Set;

/**
* <p>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.</p>
*
* <p>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.</p>
*
* @see net.sourceforge.stripes.action.UrlBinding
* @author Tim Fennell
*/
public class AnnotatedClassActionResolver implements ActionResolver {
    /**
     * Configuration key used to lookup a comma-separated list of patterns that are used to
     * restrict the set of URLs in the classpath that are searched for ActionBean classes.
     */
    @Deprecated private static final String URL_FILTERS = "ActionResolver.UrlFilters";

    /**
     * Configuration key used to lookup a comma-separated list of patterns that are used to
     * restrict the packages that will be scanned for ActionBean classes.
     */
    @Deprecated private static final String PACKAGE_FILTERS = "ActionResolver.PackageFilters";

    /**
     * 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;

    /**
     * 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<Class<? extends ActionBean>,Map<String,Method>> eventMappings =
        new HashMap<Class<? extends ActionBean>,Map<String,Method>>() {
            private static final long serialVersionUID = 1L;

            @Override
            public Map<String, Method> get(Object key) {
                Map<String, Method> 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);
        }
    }

    /**
     * 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 = UrlBindingFactory.getInstance().getBindingPrototype(clazz);
        if (proto == null) {
            UrlBindingFactory.getInstance().addBinding(clazz, new UrlBinding(clazz, binding));
        }

        // Only process the class if it's properly annotated
        if (binding != null) {
            // Construct the mapping of event->method for the class
            Map<String, Method> classMappings = new HashMap<String, Method>();
            processMethods(clazz, classMappings);

            // Put the event->method mapping for the class into the set of mappings
            this.eventMappings.put(clazz, classMappings);

            // Print out the event mappings nicely
            for (Map.Entry<String, Method> 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);
            }
        }
    }

    /**
     * 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) {
            UrlBindingFactory.getInstance().removeBinding(clazz);
        }
        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 = UrlBindingFactory.getInstance().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 = UrlBindingFactory.getInstance().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<String,Method> 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()) ) {
                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);
                }
            }
        }
    }

    /**
     * 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;
        }
    }

    /**
     * <p>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.</p>
     *
     * @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 = UrlBindingFactory.getInstance().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;
    }

    /**
     * Simple helper method that extracts the servlet path and any extra path info
     * and puts them back together handling nulls correctly.
     *
     * @param request the current HttpServletRequest
     * @return the servlet-context relative path that is being requested
     * @deprecated Use {@link HttpUtil#getRequestedPath(HttpServletRequest)} instead.
     */
    @Deprecated
    protected String getRequestedPath(HttpServletRequest request) {
        String servletPath = null, pathInfo = null;

        // Check to see if the request is processing an include, and pull the path
        // information from the appropriate source.
        if (request.getAttribute(StripesConstants.REQ_ATTR_INCLUDE_PATH) != null) {
            servletPath = (String) request.getAttribute(StripesConstants.REQ_ATTR_INCLUDE_PATH);
            pathInfo    = (String) request.getAttribute(StripesConstants.REQ_ATTR_INCLUDE_PATH_INFO);
        }
        else {
            servletPath = request.getServletPath();
            pathInfo    = request.getPathInfo();
        }

        return (servletPath == null ? "" : servletPath) + (pathInfo == null ? "" : pathInfo);
    }

    /**
     * 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<ActionBean> 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, UrlBindingFactory.getInstance().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);
            log.error(sse);
            throw sse;
        }

        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);
        }
    }

    /**
     * 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.");
        }
    }

    /**
     * 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 type.newInstance();
    }


    /**
     * <p>
     * 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.
     * </p>
     *
     * <p>
     * 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.
     * </p>
     *
     * <p>
     * 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.
     * </p>
     *
     * <p>
     * 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.
     * </p>
     *
     * @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<String> eventParams = new ArrayList<String>();
        Map<String,String[]> 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.");
        }
    }

    /**
     * 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<String,Method> mappings = this.eventMappings.get(bean);
        String path = HttpUtil.getRequestedPath(context.getRequest());
        UrlBinding prototype = UrlBindingFactory.getInstance().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.
            }
        }

        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<String,Method> 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;
    }

    /**
     * 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<String,Method> 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;
        }

        // 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<Class<? extends ActionBean>> findClasses() {
        BootstrapPropertyResolver bootstrap = getConfiguration().getBootstrapPropertyResolver();
        if (bootstrap.getProperty(URL_FILTERS) != null || bootstrap.getProperty(PACKAGE_FILTERS) != null) {
            log.error("The configuration properties '", URL_FILTERS, "' and '", PACKAGE_FILTERS,
                      "' are deprecated, and NO LONGER SUPPORTED. Please read the upgrade ",
                      "documentation for Stripes 1.5 for how to resolve this situation. In short ",
                      "you should specify neither ", URL_FILTERS, " or ", PACKAGE_FILTERS,
                      ". Instead you should specify a comma separated list of package roots ",
                      "(e.g. com.myco.web) that should be scanned for implementations of ",
                      "ActionBean, using the configuration parameter '", PACKAGES,  "'.");
        }

        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<ActionBean> resolver = new ResolverUtil<ActionBean>();
        resolver.findImplementations(ActionBean.class, pkgs);
        return resolver.getClasses();
    }

    /**
     * Get all the classes implementing {@link ActionBean} that are recognized by this
     * {@link ActionResolver}.
     */
    public Collection<Class<? extends ActionBean>> getActionBeanClasses() {
        return UrlBindingFactory.getInstance().getActionBeanClasses();
    }
}
TOP

Related Classes of net.sourceforge.stripes.controller.AnnotatedClassActionResolver

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.