jodd-madvoc/src/main/java/jodd/madvoc/component/MadvocController.java
public String invoke(String actionPath, final HttpServletRequest servletRequest, final HttpServletResponse servletResponse) throws Exception {
		final String originalActionPath = actionPath;
		boolean characterEncodingSet = false;

		while (actionPath != null) {
			// build action path
			final String httpMethod = servletRequest.getMethod().toUpperCase();

			if (log.isDebugEnabled()) {
				log.debug("Action path: " + httpMethod + " " + actionPath);
			}

			actionPath = actionPathRewriter.rewrite(servletRequest, actionPath, httpMethod);

			String[] actionPathChunks = MadvocUtil.splitPathToChunks(actionPath);

			// resolve action runtime
			ActionRuntime actionRuntime = actionsManager.lookup(httpMethod, actionPathChunks);

			if (actionRuntime == null) {

				// special case!
				if (actionPath.endsWith(welcomeFile)) {
					actionPath = actionPath.substring(0, actionPath.length() - (welcomeFile.length() - 1));
					actionPathChunks = MadvocUtil.splitPathToChunks(actionPath);
					actionRuntime = actionsManager.lookup(httpMethod, actionPathChunks);
				}
				if (actionRuntime == null) {
					return originalActionPath;
				}
			}
			if (log.isDebugEnabled()) {
				log.debug("Invoke action for '" + actionPath + "' using " + actionRuntime.createActionString());
			}

			// set character encoding
			if (!characterEncodingSet && applyCharacterEncoding) {

				final String encoding = madvocEncoding.getEncoding();

				if (encoding != null) {
					servletRequest.setCharacterEncoding(encoding);
					servletResponse.setCharacterEncoding(encoding);
				}

				characterEncodingSet = true;
			}

			// create action object
			final Object action;

			if (actionRuntime.isActionHandlerDefined()) {
				action = actionRuntime.getActionHandler();
			}
			else {
				action = createAction(actionRuntime.getActionClass());
			}

			final ActionRequest actionRequest = createActionRequest(
				actionPath,
				actionPathChunks,
				actionRuntime,
				action,
				servletRequest,
				servletResponse);

			// invoke and render
			if (actionRuntime.isAsync()) {
				asyncActionExecutor.invoke(actionRequest);
			} else {
				actionRequest.invoke();
			}

			actionPath = actionRequest.getNextActionPath();
		}
		return null;
	}
public String invoke(String actionPath, final HttpServletRequest servletRequest, final HttpServletResponse servletResponse) throws Exception {
		final String originalActionPath = actionPath;
		boolean characterEncodingSet = false;

		while (actionPath != null) {
			// build action path
			final String httpMethod = servletRequest.getMethod().toUpperCase();

			if (log.isDebugEnabled()) {
				log.debug("Action path: " + httpMethod + " " + actionPath);
			}

			actionPath = actionPathRewriter.rewrite(servletRequest, actionPath, httpMethod);

			String[] actionPathChunks = MadvocUtil.splitPathToChunks(actionPath);

			// resolve action runtime
			ActionRuntime actionRuntime = actionsManager.lookup(httpMethod, actionPathChunks);

			if (actionRuntime == null) {

				// special case!
				if (actionPath.endsWith(welcomeFile)) {
					actionPath = actionPath.substring(0, actionPath.length() - (welcomeFile.length() - 1));
					actionPathChunks = MadvocUtil.splitPathToChunks(actionPath);
					actionRuntime = actionsManager.lookup(httpMethod, actionPathChunks);
				}
				if (actionRuntime == null) {
					return originalActionPath;
				}
			}
			if (log.isDebugEnabled()) {
				log.debug("Invoke action for '" + actionPath + "' using " + actionRuntime.createActionString());
			}

			// set character encoding
			if (!characterEncodingSet && applyCharacterEncoding) {

				final String encoding = madvocEncoding.getEncoding();

				if (encoding != null) {
					servletRequest.setCharacterEncoding(encoding);
					servletResponse.setCharacterEncoding(encoding);
				}

				characterEncodingSet = true;
			}

			// create action object
			final Object action;

			if (actionRuntime.isActionHandlerDefined()) {
				action = actionRuntime.getActionHandler();
			}
			else {
				action = createAction(actionRuntime.getActionClass());
			}

			final ActionRequest actionRequest = createActionRequest(
				actionPath,
				actionPathChunks,
				actionRuntime,
				action,
				servletRequest,
				servletResponse);

			// invoke and render
			if (actionRuntime.isAsync()) {
				asyncActionExecutor.invoke(actionRequest);
			} else {
				actionRequest.invoke();
			}

			actionPath = actionRequest.getNextActionPath();
		}
		return null;
	}
Invokes action registered to provided action path, Provides action chaining, by invoking the next action request.
Returns <code>null</code> if action path is consumed and has been invoked by this controller; otherwise
the action path string is returned (it might be different than original one, provided in arguments).
On first invoke, initializes the action runtime before further proceeding.