/*
* Weblounge: Web Content Management System
* Copyright (c) 2003 - 2011 The Weblounge Team
* http://entwinemedia.com/weblounge
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package ch.entwine.weblounge.dispatcher.impl.handler;
import ch.entwine.weblounge.common.content.Resource;
import ch.entwine.weblounge.common.content.ResourceURI;
import ch.entwine.weblounge.common.content.page.Composer;
import ch.entwine.weblounge.common.content.page.HTMLHeadElement;
import ch.entwine.weblounge.common.content.page.HTMLInclude;
import ch.entwine.weblounge.common.content.page.Page;
import ch.entwine.weblounge.common.content.page.PageTemplate;
import ch.entwine.weblounge.common.impl.content.page.ComposerImpl;
import ch.entwine.weblounge.common.impl.content.page.PageURIImpl;
import ch.entwine.weblounge.common.impl.request.CacheTagSet;
import ch.entwine.weblounge.common.impl.request.Http11Constants;
import ch.entwine.weblounge.common.impl.request.Http11Utils;
import ch.entwine.weblounge.common.impl.site.ActionPool;
import ch.entwine.weblounge.common.impl.url.UrlMatcherImpl;
import ch.entwine.weblounge.common.impl.url.WebUrlImpl;
import ch.entwine.weblounge.common.repository.ContentRepository;
import ch.entwine.weblounge.common.repository.ContentRepositoryException;
import ch.entwine.weblounge.common.request.CacheTag;
import ch.entwine.weblounge.common.request.RequestFlavor;
import ch.entwine.weblounge.common.request.ResponseCache;
import ch.entwine.weblounge.common.request.WebloungeRequest;
import ch.entwine.weblounge.common.request.WebloungeResponse;
import ch.entwine.weblounge.common.site.Action;
import ch.entwine.weblounge.common.site.ActionException;
import ch.entwine.weblounge.common.site.HTMLAction;
import ch.entwine.weblounge.common.site.JSONAction;
import ch.entwine.weblounge.common.site.Site;
import ch.entwine.weblounge.common.site.XMLAction;
import ch.entwine.weblounge.common.url.UrlMatcher;
import ch.entwine.weblounge.common.url.WebUrl;
import ch.entwine.weblounge.dispatcher.ActionRequestHandler;
import ch.entwine.weblounge.dispatcher.impl.DispatchUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.EOFException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletResponse;
/**
* This handler can be used to register {@link Action}s. If a request matches
* the url space of registered action, it will handle the request by forwarding
* it to the action.
*/
public final class ActionRequestHandlerImpl implements ActionRequestHandler {
/** Logging facility */
private static final Logger logger = LoggerFactory.getLogger(ActionRequestHandlerImpl.class);
/** The registered actions */
private Map<UrlMatcher, ActionPool> actions = null;
/** Known urls */
private Map<String, ActionPool> urlCache = null;
/**
* Creates a new action request handler.
*/
public ActionRequestHandlerImpl() {
actions = new HashMap<UrlMatcher, ActionPool>();
urlCache = new HashMap<String, ActionPool>();
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.dispatcher.RequestHandler#service(ch.entwine.weblounge.common.request.WebloungeRequest,
* ch.entwine.weblounge.common.request.WebloungeResponse)
*/
public boolean service(WebloungeRequest request, WebloungeResponse response) {
WebUrl url = request.getUrl();
RequestFlavor flavor = request.getFlavor();
Mode processingMode = Mode.Default;
// Try to get hold of an action pool
ActionPool pool = null;
pool = getActionForUrl(url, flavor);
if (pool == null) {
logger.debug("No action found to handle {}", url);
return false;
}
// Match! Let's try to get an actual action from that pool
Action action = null;
try {
action = (Action) pool.borrowObject();
} catch (Throwable t) {
logger.error("Error getting action from action pool", t);
DispatchUtils.sendInternalError(request, response);
return true;
}
// Make sure the action is returned to the pool no matter what
try {
// Check the request method. We won't handle just everything
String requestMethod = request.getMethod().toUpperCase();
if (!action.supportsMethod(requestMethod)) {
if ("OPTIONS".equals(requestMethod)) {
StringBuffer verbs = new StringBuffer();
for (String verb : action.getMethods()) {
if (verbs.length() > 0)
verbs.append(",");
verbs.append(verb);
}
logger.trace("Answering options request to {} with {}", action, verbs.toString());
response.setHeader("Allow", verbs.toString());
response.setContentLength(0);
return true;
} else {
logger.debug("Action {} does not support {} requests", action, requestMethod);
DispatchUtils.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, request, response);
return true;
}
}
// Check for explicit no cache instructions
boolean noCache = request.getParameter(ResponseCache.NOCACHE_PARAM) != null;
// Check if the page is already part of the cache. If so, our task is
// already done!
if (!noCache && request.getVersion() == Resource.LIVE) {
long expirationTime = action.getCacheExpirationTime();
long revalidationTime = action.getClientRevalidationTime();
// Create the set of tags that identify the request output
CacheTagSet cacheTags = createCacheTags(request, action);
// Check if the page is already part of the cache
if (response.startResponse(cacheTags.getTags(), expirationTime, revalidationTime)) {
logger.debug("Action answered request for {} from cache", request.getUrl());
return true;
}
processingMode = Mode.Cached;
} else if (Http11Constants.METHOD_HEAD.equals(request.getMethod())) {
// handle HEAD requests
Http11Utils.startHeadResponse(response);
processingMode = Mode.Head;
} else if (request.getVersion() == Resource.WORK) {
response.setCacheExpirationTime(0);
}
logger.debug("Action {} will handle {}", action, url);
// Call the service method depending on the flavor
switch (flavor) {
case HTML:
if (!action.supportsFlavor(flavor) && !(action instanceof HTMLAction))
return false;
serveHTML(action, request, response);
break;
case XML:
if (!action.supportsFlavor(flavor) && !(action instanceof XMLAction))
return false;
serveXML(action, request, response);
break;
case JSON:
if (!action.supportsFlavor(flavor) && !(action instanceof JSONAction))
return false;
serveJSON(action, request, response);
break;
default:
if (action.supportsFlavor(RequestFlavor.HTML) || action instanceof HTMLAction)
serveHTML(action, request, response);
else if (action.supportsFlavor(RequestFlavor.XML) || action instanceof XMLAction)
serveXML(action, request, response);
else if (action.supportsFlavor(RequestFlavor.JSON) || action instanceof JSONAction)
serveJSON(action, request, response);
else
serveGeneric(action, request, response);
}
} finally {
// Finish cache handling
switch (processingMode) {
case Cached:
response.endResponse();
break;
case Head:
Http11Utils.endHeadResponse(response);
break;
default:
break;
}
// Return the action handler to the pool
try {
pool.returnObject(action);
} catch (Throwable t) {
logger.error("Error returning action {} to pool: {}", new Object[] {
action,
t.getMessage(),
t });
}
}
return true;
}
/**
* This method has the action serve the <code>HTML</code> flavor.
*
* @param action
* the action
* @param request
* the http request
* @param response
* the http response
*/
private void serveHTML(Action action, WebloungeRequest request,
WebloungeResponse response) {
WebUrl url = request.getUrl();
Site site = request.getSite();
// Load the target page used to render the action
Page page = null;
try {
page = getTargetPage(action, request);
request.setAttribute(WebloungeRequest.PAGE, page);
// TODO: Check access rights with action configuration
} catch (ContentRepositoryException e) {
logger.error("Error loading target page for action {} at {}", action, url);
DispatchUtils.sendInternalError(request, response);
return;
}
// Get hold of the page template
PageTemplate template = null;
try {
template = getTargetTemplate(action, page, request);
if (template == null)
template = site.getDefaultTemplate();
} catch (IllegalStateException e) {
logger.warn(e.getMessage());
DispatchUtils.sendInternalError(request, response);
}
// Finally, let's get some work done!
try {
request.setAttribute(WebloungeRequest.ACTION, action);
// Add the action's HTML header elements to the response if it's not
// only used in editing mode
for (HTMLHeadElement header : action.getHTMLHeaders()) {
if (!HTMLInclude.Use.Editor.equals(header.getUse()))
response.addHTMLHeader(header);
}
// Prepare the action by storing initial values for page and template
if (action instanceof HTMLAction) {
((HTMLAction) action).setPage(page);
((HTMLAction) action).setTemplate(template);
}
// Set an appropriate content type
response.setContentType("text/html");
// Ask the action to get started and validate the request
action.configure(request, response, RequestFlavor.HTML);
// See if the action cares about the response's modification date. If not,
// we do, even though we don't know exactly.
// response.getModificationDate()
// will return either the date that has been set or the current date, both
// of which is fine with us.
response.setModificationDate(response.getModificationDate());
// Store values that may have been updated by the action during
// configure()
if (action instanceof HTMLAction) {
request.setAttribute(WebloungeRequest.PAGE, ((HTMLAction) action).getPage());
request.setAttribute(WebloungeRequest.TEMPLATE, ((HTMLAction) action).getTemplate());
} else {
request.setAttribute(WebloungeRequest.PAGE, page);
request.setAttribute(WebloungeRequest.TEMPLATE, template);
}
// Have the content delivered
if (action.startResponse(request, response) == Action.EVAL_REQUEST) {
if (page != null) {
logger.trace("Rendering action '{}' on page {}", action, page);
PageRequestHandlerImpl.getInstance().service(request, response);
} else {
logger.trace("Rendering action '{}' on ad-hoc page", action);
response.getWriter().println("<!DOCTYPE HTML>");
response.getWriter().println("<html>\n\t<head>");
if (action instanceof HTMLAction) {
((HTMLAction) action).startHeader(request, response);
}
response.getWriter().println("\t</head>\n\t<body>");
if (action instanceof HTMLAction) {
Composer c = new ComposerImpl("stage");
((HTMLAction) action).startStage(request, response, c);
}
response.getWriter().print("\n\t</body>\n</html>");
}
}
} catch (EOFException e) {
logger.debug("Error writing action '{}' back to client: connection closed by client", url);
} catch (IOException e) {
logger.error("Error writing action output to client: {}", e.getMessage());
} catch (ActionException e) {
logger.warn("Error processing action '{}' for {}: {}", new Object[] {
action,
request.getUrl(),
e.getMessage() });
DispatchUtils.sendError(e.getStatusCode(), request, response);
} catch (Throwable e) {
logger.error("Error processing action '{}' for {}", action, request.getUrl());
logger.error(e.getMessage(), e);
DispatchUtils.sendInternalError(request, response);
} finally {
request.removeAttribute(WebloungeRequest.ACTION);
request.removeAttribute(WebloungeRequest.PAGE);
}
}
/**
* This method has the action serve the <code>XML</code> flavor.
*
* @param action
* the action
* @param request
* the http request
* @param response
* the http response
*/
private void serveXML(Action action, WebloungeRequest request,
WebloungeResponse response) {
try {
// Set an appropriate content type
response.setContentType("text/xml");
// Ask the action to get started and validate the request
action.configure(request, response, RequestFlavor.XML);
// See if the action cares about the response's modification date. If not,
// we do, even though we don't know exactly.
// response.getModificationDate()
// will return either the date that has been set or the current date, both
// of which is fine with us.
response.setModificationDate(response.getModificationDate());
// Have the content delivered
if (action.startResponse(request, response) == Action.EVAL_REQUEST) {
if (action instanceof XMLAction) {
((XMLAction) action).startXML(request, response);
}
}
} catch (EOFException e) {
logger.debug("Error writing action '{}' back to client: connection closed by client", request.getUrl());
} catch (IOException e) {
logger.debug("Error writing action output to client: {}", e.getMessage());
} catch (ActionException e) {
logger.error("Error processing action '{}' for {}: {}", new Object[] {
action,
request.getUrl(),
e.getMessage() });
DispatchUtils.sendError(e.getStatusCode(), request, response);
} catch (Throwable e) {
logger.error("Error processing action '{}' for {}", action, request.getUrl());
logger.error(e.getMessage(), e);
DispatchUtils.sendInternalError(request, response);
}
}
/**
* This method has the action serve the <code>JSON</code> flavor.
*
* @param action
* the action
* @param request
* the http request
* @param response
* the http response
*/
private void serveJSON(Action action, WebloungeRequest request,
WebloungeResponse response) {
try {
// Set an appropriate content type
response.setContentType("text/json");
// Ask the action to get started and validate the request
action.configure(request, response, RequestFlavor.JSON);
// See if the action cares about the response's modification date. If not,
// we do, even though we don't know exactly.
// response.getModificationDate()
// will return either the date that has been set or the current date, both
// of which is fine with us.
response.setModificationDate(response.getModificationDate());
// Have the content delivered
if (action.startResponse(request, response) == Action.EVAL_REQUEST) {
if (action instanceof JSONAction) {
((JSONAction) action).startJSON(request, response);
}
}
} catch (EOFException e) {
logger.debug("Error writing action '{}' back to client: connection closed by client", request.getUrl());
} catch (IOException e) {
logger.debug("Error writing action output to client: {}", e.getMessage());
} catch (ActionException e) {
logger.error("Error processing action '{}' for {}: {}", new Object[] {
action,
request.getUrl(),
e.getMessage() });
DispatchUtils.sendError(e.getStatusCode(), request, response);
} catch (Throwable e) {
logger.error("Error processing action '{}' for {}", action, request.getUrl());
logger.error(e.getMessage(), e);
DispatchUtils.sendInternalError(request, response);
}
}
/**
* This method will call
* {@link Action#configure(WebloungeRequest, WebloungeResponse, RequestFlavor)}
* , then {@link Action#startResponse(WebloungeRequest, WebloungeResponse)}
* and be done with it.
*
* @param action
* the action
* @param request
* the http request
* @param response
* the http response
*/
private void serveGeneric(Action action, WebloungeRequest request,
WebloungeResponse response) {
try {
action.configure(request, response, RequestFlavor.ANY);
// See if the action cares about the response's modification date. If not,
// we do, even though we don't know exactly.
// response.getModificationDate()
// will return either the date that has been set or the current date, both
// of which is fine with us.
response.setModificationDate(response.getModificationDate());
action.startResponse(request, response);
} catch (EOFException e) {
logger.debug("Error writing action '{}' back to client: connection closed by client", request.getUrl());
} catch (IOException e) {
logger.debug("Error writing action output to client: {}", e.getMessage());
} catch (ActionException e) {
logger.error("Error processing action '{}' for {}: {}", new Object[] {
action,
request.getUrl(),
e.getMessage() });
DispatchUtils.sendError(e.getStatusCode(), request, response);
} catch (Throwable e) {
logger.error("Error processing action '{}' for {}", action, request.getUrl());
logger.error(e.getMessage(), e);
DispatchUtils.sendInternalError(request, response);
}
}
/**
* Returns the primary set of cache tags for the given request and action.
*
* @param request
* the request
* @param action
* the action
* @return the cache tags
*/
protected CacheTagSet createCacheTags(WebloungeRequest request, Action action) {
CacheTagSet cacheTags = new CacheTagSet();
cacheTags.add(CacheTag.Url, request.getUrl().getPath());
cacheTags.add(CacheTag.Url, request.getRequestedUrl().getPath());
cacheTags.add(CacheTag.Language, request.getLanguage().getIdentifier());
cacheTags.add(CacheTag.User, request.getUser().getLogin());
cacheTags.add(CacheTag.Module, action.getModule().getIdentifier());
cacheTags.add(CacheTag.Action, action.getIdentifier());
Enumeration<?> pe = request.getParameterNames();
int parameterCount = 0;
while (pe.hasMoreElements()) {
parameterCount++;
String key = pe.nextElement().toString();
String[] values = request.getParameterValues(key);
for (String value : values) {
cacheTags.add(key, value);
}
}
cacheTags.add(CacheTag.Parameters, Integer.toString(parameterCount));
return cacheTags;
}
/**
* Returns the template that will be used to handle this request. If a
* template was specified in the request but cannot be found or used for some
* reason, an {@link IllegalStateException} is thrown.
*
* @param action
* the action
* @param page
* the page
* @param request
* the request
*
* @return the template
* @throws IllegalStateException
* if the template cannot be found
*/
protected PageTemplate getTargetTemplate(Action action, Page page,
WebloungeRequest request) throws IllegalStateException {
Site site = request.getSite();
PageTemplate template = null;
String templateId = null;
// Does the request specify an ad-hoc template?
if (request.getAttribute(WebloungeRequest.TEMPLATE) != null) {
templateId = (String) request.getAttribute(WebloungeRequest.TEMPLATE);
template = site.getTemplate(templateId);
if (template == null)
throw new IllegalStateException("Page template '" + templateId + "' specified by request attribute was not found");
}
// Does the request specify an ad-hoc template?
if (template == null && request.getParameter(HTMLAction.TARGET_TEMPLATE) != null) {
templateId = request.getParameter(HTMLAction.TARGET_TEMPLATE);
template = site.getTemplate(templateId);
if (template == null)
throw new IllegalStateException("Page template '" + templateId + "' specified by request parameter was not found");
}
// See if the action handler specifies a template
if (template == null && action instanceof HTMLAction) {
HTMLAction htmlAction = (HTMLAction) action;
template = htmlAction.getTemplate();
if (template == null)
logger.debug("Action '{}' did not define a page template", action.getIdentifier());
}
// See if the action handler specifies a default template
if (template == null && action instanceof HTMLAction) {
HTMLAction htmlAction = (HTMLAction) action;
template = htmlAction.getDefaultTemplate();
if (template == null)
logger.debug("Action '{}' did not define a default page template", action.getIdentifier());
}
// By default, the page will have to deliver on the template
if (template == null && page != null) {
template = site.getTemplate(page.getTemplate());
if (template == null)
throw new IllegalStateException("Page template '" + page.getTemplate() + "' for page '" + page + "' was not found");
}
// Did we end up finding a template?
if (template == null)
return null;
template.setEnvironment(request.getEnvironment());
return template;
}
/**
* Tries to determine the target page for the action result. The
* <code>target-page</code> request parameter will be considered as well as
* the action configuration. In any case, the site's homepage will be the
* fallback.
* <p>
* Should a target page be configured either through the request or through
* action configuration, and should that url not be present, this method will
* return <code>null</code>.
*
* @param action
* the action
* @param request
* the weblounge request
* @return the target page
* @throws ContentRepositoryException
* if the target page cannot be loaded
*/
protected Page getTargetPage(Action action, WebloungeRequest request)
throws ContentRepositoryException {
ResourceURI target = null;
Page page = null;
Site site = request.getSite();
boolean targetForced = false;
// Check if a target-page parameter was passed
String targetPage = request.getParameter(HTMLAction.TARGET_PAGE);
if (targetPage != null) {
targetForced = true;
try {
String decocedTargetUrl = null;
String encoding = request.getCharacterEncoding();
if (encoding == null)
encoding = "utf-8";
decocedTargetUrl = URLDecoder.decode(targetPage, encoding);
target = new PageURIImpl(site, decocedTargetUrl);
} catch (UnsupportedEncodingException e) {
logger.warn("Error while decoding target url {}: {}", targetPage, e.getMessage());
target = new PageURIImpl(site, "/");
}
}
// Check the action configuration
else if (action instanceof HTMLAction) {
HTMLAction htmlAction = (HTMLAction) action;
if (htmlAction.getPageURI() != null) {
target = htmlAction.getPageURI();
targetForced = true;
}
}
// Nothing found, let's choose the site's homepage
if (target == null) {
target = new PageURIImpl(site, "/");
}
// We are about to render the action output in the composers of the target
// page. This is why we have to make sure that this target page exists,
// otherwise the user will get a 404.
ContentRepository contentRepository = site.getContentRepository();
if (contentRepository == null) {
logger.warn("Content repository not available to read target page for action '{}'", action, target);
return null;
}
// Does the page exist?
page = (Page) contentRepository.get(target);
if (page == null) {
if (targetForced) {
logger.warn("Output of action '{}' is configured to render on non existing page {}", action, target);
return null;
}
// Fall back to site homepage
target = new PageURIImpl(site, "/");
page = (Page) contentRepository.get(target);
if (page == null) {
logger.debug("Site {} has no homepage as fallback to render actions", site);
return null;
}
}
return page;
}
/**
* Returns the action that is registered to serve the given url or
* <code>null</code> if no such handler exists.
*
* @param url
* the url
* @param flavor
* the request flavor
* @return the handler
*/
private ActionPool getActionForUrl(WebUrl url, RequestFlavor flavor) {
StringBuilder normalizedUrlBuilder = new StringBuilder(url.getSite().getIdentifier());
normalizedUrlBuilder.append("://").append(url.normalize(false, false, true));
// Try to use the url cache
ActionPool actionPool = urlCache.get(normalizedUrlBuilder.toString());
if (actionPool != null)
return actionPool;
// Nothing is in the cache, let's see if this is simply the first time
// that this action is being called
int maxMatchLength = 0;
for (Entry<UrlMatcher, ActionPool> entry : actions.entrySet()) {
UrlMatcher matcher = entry.getKey();
if (matcher.matches(url, flavor)) {
ActionPool pool = entry.getValue();
int matchLength = matcher.getMountpoint().length();
if (matchLength > maxMatchLength) {
maxMatchLength = matchLength;
actionPool = pool;
}
}
}
// Still nothing?
if (actionPool == null) {
logger.debug("No action registered to handle {}", url);
return null;
}
// Register the url for future reference
urlCache.put(normalizedUrlBuilder.toString(), actionPool);
return actionPool;
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.dispatcher.ActionRequestHandler#register(ch.entwine.weblounge.common.site.Action)
*/
public void register(Action action) {
if (action == null)
throw new IllegalArgumentException("Action configuration cannot be null");
// Create a url matcher
UrlMatcher matcher = new UrlMatcherImpl(action);
ActionPool pool = new ActionPool(action);
StringBuffer registration = new StringBuffer(new WebUrlImpl(action.getSite(), action.getPath()).normalize());
// Register the action
synchronized (actions) {
actions.put(matcher, pool);
}
// Cache the action urls
StringBuffer flavors = new StringBuffer();
synchronized (urlCache) {
for (RequestFlavor flavor : action.getFlavors()) {
WebUrl actionUrl = new WebUrlImpl(action.getSite(), action.getPath(), Resource.LIVE, flavor);
String normalizedUrl = actionUrl.normalize(false, false, true);
urlCache.put(normalizedUrl, pool);
if (flavors.length() > 0)
flavors.append(",");
flavors.append(flavor.toString().toLowerCase());
logger.trace("Caching action '{}' for url {}", action, normalizedUrl);
}
}
logger.debug("Action '{}' ({}) registered for site://{}", new Object[] {
action,
flavors.toString(),
registration.toString() });
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.dispatcher.ActionRequestHandler#unregister(ch.entwine.weblounge.common.site.Action)
*/
public boolean unregister(Action action) {
ActionPool pool = null;
// Remove the pool from the actions registry
synchronized (actions) {
UrlMatcher matcher = new UrlMatcherImpl(action);
pool = actions.remove(matcher);
if (pool == null) {
logger.warn("Tried to unregister unknown action '{}'", action);
return false;
}
}
// Remove entries from the url cache
synchronized (urlCache) {
Iterator<Entry<String, ActionPool>> cacheIterator = urlCache.entrySet().iterator();
while (cacheIterator.hasNext()) {
ActionPool candidate = cacheIterator.next().getValue();
if (candidate.equals(pool)) {
logger.trace("Removing '{}' from action url cache", action);
cacheIterator.remove();
}
}
}
logger.debug("Unregistering action '{}' from {}", action, new WebUrlImpl(action.getSite(), action.getPath()).normalize());
return true;
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.dispatcher.RequestHandler#getName()
*/
public String getName() {
return "action request handler";
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return getName();
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.dispatcher.RequestHandler#getPriority()
*/
public int getPriority() {
return 0;
}
}