blob: bcbe58a60bc4cb07f6bf47809bd41610c1c9a1c0 [file] [log] [blame]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="Date-Revision-yyyymmdd" content="20140918"/>
<meta http-equiv="Content-Language" content="en"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>RestfulActionMapper</title>
<link href="//fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,400italic,600italic,700italic" rel="stylesheet" type="text/css">
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<link href="/css/main.css" rel="stylesheet">
<link href="/css/custom.css" rel="stylesheet">
<link href="/css/syntax.css" rel="stylesheet">
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
<script type="text/javascript" src="/bootstrap/js/bootstrap.js"></script>
<script type="text/javascript" src="/js/community.js"></script>
<!-- Matomo -->
<script>
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
/* We explicitly disable cookie tracking to avoid privacy issues */
_paq.push(['disableCookies']);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//analytics.apache.org/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '41']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<!-- End Matomo Code -->
</head>
<body>
<a href="https://github.com/apache/struts" class="github-ribbon">
<img decoding="async" loading="lazy" style="position: absolute; right: 0; border: 0;" width="149" height="149" src="https://github.blog/wp-content/uploads/2008/12/forkme_right_red_aa0000.png?resize=149%2C149" class="attachment-full size-full" alt="Fork me on GitHub" data-recalc-dims="1">
</a>
<header>
<nav>
<div role="navigation" class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" data-toggle="collapse" data-target="#struts-menu" class="navbar-toggle">
Menu
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="/index.html" class="navbar-brand logo"><img src="/img/struts-logo.svg"></a>
</div>
<div id="struts-menu" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="dropdown">
<a data-toggle="dropdown" href="#" class="dropdown-toggle">
Home<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><a href="/index.html">Welcome</a></li>
<li><a href="/download.cgi">Download</a></li>
<li><a href="/releases.html">Releases</a></li>
<li><a href="/announce-2023.html">Announcements</a></li>
<li><a href="http://www.apache.org/licenses/">License</a></li>
<li><a href="https://www.apache.org/foundation/thanks.html">Thanks!</a></li>
<li><a href="https://www.apache.org/foundation/sponsorship.html">Sponsorship</a></li>
<li><a href="https://privacy.apache.org/policies/privacy-policy-public.html">Privacy Policy</a></li>
</ul>
</li>
<li class="dropdown">
<a data-toggle="dropdown" href="#" class="dropdown-toggle">
Support<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><a href="/mail.html">User Mailing List</a></li>
<li><a href="https://issues.apache.org/jira/browse/WW">Issue Tracker</a></li>
<li><a href="/security.html">Reporting Security Issues</a></li>
<li><a href="/commercial-support.html">Commercial Support</a></li>
<li class="divider"></li>
<li><a href="https://cwiki.apache.org/confluence/display/WW/Migration+Guide">Version Notes</a></li>
<li><a href="https://cwiki.apache.org/confluence/display/WW/Security+Bulletins">Security Bulletins</a></li>
<li class="divider"></li>
<li><a href="/maven/project-info.html">Maven Project Info</a></li>
<li><a href="/maven/struts2-core/dependencies.html">Struts Core Dependencies</a></li>
<li><a href="/maven/struts2-plugins/modules.html">Plugin Dependencies</a></li>
</ul>
</li>
<li class="dropdown">
<a data-toggle="dropdown" href="#" class="dropdown-toggle">
Documentation<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><a href="/birdseye.html">Birds Eye</a></li>
<li><a href="/primer.html">Key Technologies</a></li>
<li><a href="/kickstart.html">Kickstart FAQ</a></li>
<li><a href="https://cwiki.apache.org/confluence/display/WW/Home">Wiki</a></li>
<li class="divider"></li>
<li><a href="/getting-started/">Getting Started</a></li>
<li><a href="/security/">Security Guide</a></li>
<li><a href="/core-developers/">Core Developers Guide</a></li>
<li><a href="/tag-developers/">Tag Developers Guide</a></li>
<li><a href="/maven-archetypes/">Maven Archetypes</a></li>
<li><a href="/plugins/">Plugins</a></li>
<li><a href="/maven/struts2-core/apidocs/index.html">Struts Core API</a></li>
<li><a href="/tag-developers/tag-reference.html">Tag reference</a></li>
<li><a href="https://cwiki.apache.org/confluence/display/WW/FAQs">FAQs</a></li>
<li><a href="http://cwiki.apache.org/S2PLUGINS/home.html">Plugin registry</a></li>
</ul>
</li>
<li class="dropdown">
<a data-toggle="dropdown" href="#" class="dropdown-toggle">
Contributing<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><a href="/youatstruts.html">You at Struts</a></li>
<li><a href="/helping.html">How to Help FAQ</a></li>
<li><a href="/dev-mail.html">Development Lists</a></li>
<li class="divider"></li>
<li><a href="/submitting-patches.html">Submitting patches</a></li>
<li><a href="/builds.html">Source Code and Builds</a></li>
<li><a href="/coding-standards.html">Coding standards</a></li>
<li><a href="/contributors/">Contributors Guide</a></li>
<li class="divider"></li>
<li><a href="/release-guidelines.html">Release Guidelines</a></li>
<li><a href="/bylaws.html">PMC Charter</a></li>
<li><a href="/volunteers.html">Volunteers</a></li>
<li><a href="https://gitbox.apache.org/repos/asf?p=struts.git">Source Repository</a></li>
<li><a href="/updating-website.html">Updating the website</a></li>
</ul>
</li>
<li class="apache"><a href="http://www.apache.org/"><img src="/img/apache.png"></a></li>
</ul>
</div>
</div>
</div>
</nav>
</header>
<article class="container">
<section class="col-md-12">
<a class="edit-on-gh" href="https://github.com/apache/struts-site/edit/master/source/core-developers/restful-action-mapper.md" title="Edit this page on GitHub">Edit on GitHub</a>
<a href="action-mapper.html" title="back to ActionMapper"><< back to ActionMapper</a>
<h1 class="no_toc" id="restfulactionmapper">RestfulActionMapper</h1>
<ul id="markdown-toc">
<li><a href="#restful2actionmapper" id="markdown-toc-restful2actionmapper">Restful2ActionMapper</a> <ul>
<li><a href="#example" id="markdown-toc-example">Example</a></li>
<li><a href="#unit-testing" id="markdown-toc-unit-testing">Unit testing</a></li>
</ul>
</li>
</ul>
<p>A custom action mapper using the following format:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://HOST/ACTION_NAME/PARAM_NAME1/PARAM_VALUE1/PARAM_NAME2/PARAM_VALUE2
</code></pre></div></div>
<p>You can have as many parameters you’d like to use. Alternatively the URL can be shortened to the following:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://HOST/ACTION_NAME/PARAM_VALUE1/PARAM_NAME2/PARAM_VALUE2
</code></pre></div></div>
<p>This is the same as:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://HOST/ACTION_NAME/ACTION_NAME + "Id"/PARAM_VALUE1/PARAM_NAME2/PARAM_VALUE2
</code></pre></div></div>
<p>Suppose for example we would like to display some articles by id at using the following URL sheme:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://HOST/article/Id
</code></pre></div></div>
<p>Your action just needs a <code class="language-plaintext highlighter-rouge">setArticleId()</code> method, and requests such as <code class="language-plaintext highlighter-rouge">/article/1</code>, <code class="language-plaintext highlighter-rouge">/article/2</code>, etc will all map
to that URL pattern.</p>
<h2 id="restful2actionmapper">Restful2ActionMapper</h2>
<p>Improved restful action mapper that adds several REST-style improvements to action mapping, but supports fully-customized
URL’s via XML. The two primary REST enhancements are:</p>
<ul>
<li>If the method is not specified (via <code class="language-plaintext highlighter-rouge">!</code> or <code class="language-plaintext highlighter-rouge">method:</code> prefix), the method is <em>guessed</em> at using REST-style conventions
that examine the URL and the HTTP method.</li>
<li>Parameters are extracted from the action name, if parameter name/value pairs are specified using <code class="language-plaintext highlighter-rouge">PARAM_NAME/PARAM_VALUE</code> syntax.</li>
</ul>
<p>These two improvements allow a GET request for <code class="language-plaintext highlighter-rouge">category/action/movie/Thrillers</code> to be mapped to the action
name <code class="language-plaintext highlighter-rouge">movie</code> with an id of <code class="language-plaintext highlighter-rouge">Thrillers</code> with an extra parameter named <code class="language-plaintext highlighter-rouge">category</code> with a value of <code class="language-plaintext highlighter-rouge">action</code>. A single action
mapping can then handle all CRUD operations using wildcards, e.g.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;action</span> <span class="na">name=</span><span class="s">"movie/*"</span> <span class="na">className=</span><span class="s">"app.MovieAction"</span><span class="nt">&gt;</span>
<span class="nt">&lt;param</span> <span class="na">name=</span><span class="s">"id"</span><span class="nt">&gt;</span>{1}<span class="nt">&lt;/param&gt;</span>
...
<span class="nt">&lt;/action&gt;</span>
</code></pre></div></div>
<p>This mapper supports the following parameters:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">struts.mapper.idParameterName</code> - if set, this value will be the name of the parameter under which the id is stored.
The id will then be removed from the action name. This allows restful actions to not require wildcards.</li>
</ul>
<p>The following URL’s will invoke its methods:</p>
<table>
<thead>
<tr>
<th>Request</th>
<th>method called</th>
</tr>
</thead>
<tbody>
<tr>
<td>GET: <code class="language-plaintext highlighter-rouge">/movie/</code></td>
<td>method=”index”</td>
</tr>
<tr>
<td>GET: <code class="language-plaintext highlighter-rouge">/movie/Thrillers</code></td>
<td>method=”view”, id=”Thrillers”</td>
</tr>
<tr>
<td>GET: <code class="language-plaintext highlighter-rouge">/movie/Thrillers!edit</code></td>
<td>method=”edit”, id=”Thrillers”</td>
</tr>
<tr>
<td>GET: <code class="language-plaintext highlighter-rouge">/movie/new</code></td>
<td>method=”editNew”</td>
</tr>
<tr>
<td>POST: <code class="language-plaintext highlighter-rouge">/movie/</code></td>
<td>method=”create”</td>
</tr>
<tr>
<td>PUT: <code class="language-plaintext highlighter-rouge">/movie/Thrillers</code></td>
<td>method=”update”, id=”Thrillers”</td>
</tr>
<tr>
<td>DELETE: <code class="language-plaintext highlighter-rouge">/movie/Thrillers</code></td>
<td>method=”remove”, id=”Thrillers”</td>
</tr>
</tbody>
</table>
<p>To simulate the HTTP methods <code class="language-plaintext highlighter-rouge">PUT</code> and <code class="language-plaintext highlighter-rouge">DELETE</code>, since they aren’t supported by HTML, the HTTP parameter <code class="language-plaintext highlighter-rouge">__http_method</code>
will be used.</p>
<p>The syntax and design for this feature was inspired by the REST support in Ruby on Rails.
See <a href="http://ryandaigle.com/articles/2006/08/01/whats-new-in-edge-rails-simply-restful-support-and-how-to-use-it">Simple RESTful support</a></p>
<h3 id="example">Example</h3>
<p>To use the <code class="language-plaintext highlighter-rouge">Restful2ActionMapper</code> in an existing struts application we have to change the <code class="language-plaintext highlighter-rouge">struts.mapper.class</code> constant
and let it point to the <code class="language-plaintext highlighter-rouge">Restful2ActionMapper</code>:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;constant</span> <span class="na">name=</span><span class="s">"struts.mapper.class"</span> <span class="na">value=</span><span class="s">"org.apache.struts2.dispatcher.mapper.Restful2ActionMapper"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>
<p>The problem with the above approach is that we may break existing actions because the <code class="language-plaintext highlighter-rouge">Restful2ActionMapper</code> tries
to guess the method name using conventions that aren’t applicable to normal action classes.</p>
<p>To overcome the above problem, we have to use a different action mapper depending on the url we want to process.
REST actions will be processed by the <code class="language-plaintext highlighter-rouge">Restful2ActionMapper</code> and non-REST actions by the <code class="language-plaintext highlighter-rouge">DefaultActionMapper</code>.</p>
<p>To achieve that we have to rely on namespaces and the <code class="language-plaintext highlighter-rouge">PrefixBasedActionMapper</code> that can choose which action mapper
to use for a particular url based on a prefix (the action namespace).</p>
<p>To put everything together, we create a package for our rest actions</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;package</span> <span class="na">name=</span><span class="s">"rest"</span> <span class="na">namespace=</span><span class="s">"/rest"</span> <span class="na">extends=</span><span class="s">"struts-default"</span><span class="nt">&gt;</span>
....interceptor config
<span class="nt">&lt;action</span> <span class="na">name=</span><span class="s">"movie/*"</span> <span class="na">class=</span><span class="s">"app.MovieAction"</span><span class="nt">&gt;</span>
<span class="nt">&lt;param</span> <span class="na">name=</span><span class="s">"id"</span><span class="nt">&gt;</span>{1}<span class="nt">&lt;/param&gt;</span>
....results
<span class="nt">&lt;/action&gt;</span>
....
<span class="nt">&lt;/package&gt;</span>
</code></pre></div></div>
<p>All other actions remain in their existing packages and namespaces we use the <code class="language-plaintext highlighter-rouge">PrefixBasedActionMapper</code> telling it to use
the <code class="language-plaintext highlighter-rouge">Restful2ActionMapper</code> for actions in the <code class="language-plaintext highlighter-rouge">/rest</code> namespace and the <code class="language-plaintext highlighter-rouge">DefaultActionMapper</code> for all other actions.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;constant</span> <span class="na">name=</span><span class="s">"struts.mapper.class"</span> <span class="na">value=</span><span class="s">"org.apache.struts2.dispatcher.mapper.PrefixBasedActionMapper"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;constant</span> <span class="na">name=</span><span class="s">"struts.mapper.prefixMapping"</span> <span class="na">value=</span><span class="s">"/rest:restful2,:struts"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>
<p>For the <code class="language-plaintext highlighter-rouge">Restful2ActionMapper</code> to work we also have to set</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;constant</span> <span class="na">name=</span><span class="s">"struts.enable.SlashesInActionNames"</span> <span class="na">value=</span><span class="s">"true"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;constant</span> <span class="na">name=</span><span class="s">"struts.mapper.alwaysSelectFullNamespace"</span> <span class="na">value=</span><span class="s">"false"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>
<h3 id="unit-testing">Unit testing</h3>
<p>Below you will find a simple unit test to test how to test actions when <code class="language-plaintext highlighter-rouge">Restful2ActionMapper</code> is used.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MovieActionTest</span> <span class="kd">extends</span> <span class="nc">StrutsJUnit4TestCase</span><span class="o">&lt;</span><span class="nc">MovieActionTest</span><span class="o">&gt;{</span>
<span class="nd">@Before</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setUp</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="c1">//assumes Basic authentication</span>
<span class="kd">super</span><span class="o">.</span><span class="na">setUp</span><span class="o">();</span>
<span class="nc">String</span> <span class="n">credentials</span> <span class="o">=</span> <span class="s">"username:password"</span><span class="o">;</span>
<span class="n">request</span><span class="o">.</span><span class="na">addHeader</span><span class="o">(</span><span class="s">"authorization"</span><span class="o">,</span> <span class="s">"BASIC "</span> <span class="o">+</span> <span class="nc">Base64</span><span class="o">.</span><span class="na">encodeBase64String</span><span class="o">(</span><span class="n">credentials</span><span class="o">.</span><span class="na">getBytes</span><span class="o">()));</span>
<span class="o">}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testIndex</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">request</span><span class="o">.</span><span class="na">setMethod</span><span class="o">(</span><span class="s">"get"</span><span class="o">);</span> <span class="c1">//Http method should be set</span>
<span class="nc">ActionProxy</span> <span class="n">proxy</span> <span class="o">=</span> <span class="n">getActionProxy</span><span class="o">(</span><span class="s">"/rest/movie/"</span><span class="o">);</span>
<span class="n">proxy</span><span class="o">.</span><span class="na">setExecuteResult</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">result</span> <span class="o">=</span> <span class="n">proxy</span><span class="o">.</span><span class="na">execute</span><span class="o">();</span>
<span class="c1">//assertions ... </span>
<span class="o">}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testView</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">request</span><span class="o">.</span><span class="na">setMethod</span><span class="o">(</span><span class="s">"get"</span><span class="o">);</span> <span class="c1">//Http method should be set</span>
<span class="nc">ActionProxy</span> <span class="n">proxy</span> <span class="o">=</span> <span class="n">getActionProxy</span><span class="o">(</span><span class="s">"/rest/movie/1"</span><span class="o">);</span>
<span class="nc">MovieAction</span> <span class="n">movieAction</span> <span class="o">=</span> <span class="nc">MovieAction</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">cast</span><span class="o">(</span><span class="n">proxy</span><span class="o">.</span><span class="na">getAction</span><span class="o">());</span>
<span class="n">proxy</span><span class="o">.</span><span class="na">setExecuteResult</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">result</span> <span class="o">=</span> <span class="n">proxy</span><span class="o">.</span><span class="na">execute</span><span class="o">();</span>
<span class="c1">//assertions ...</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="s">"1"</span><span class="o">,</span> <span class="n">movieAction</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Thanks to Antonios Gkogkakis for the examples!</p>
</section>
</article>
<footer class="container">
<div class="col-md-12">
Copyright &copy; 2000-2022 <a href="https://www.apache.org/">The Apache Software Foundation</a>.
Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts project logos are
trademarks of The Apache Software Foundation. All Rights Reserved.
</div>
<div class="col-md-12">Logo and website design donated by <a href="https://softwaremill.com/">SoftwareMill</a>.</div>
</footer>
<script>!function (d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (!d.getElementById(id)) {
js = d.createElement(s);
js.id = id;
js.src = "//platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js, fjs);
}
}(document, "script", "twitter-wjs");</script>
<script src="https://apis.google.com/js/platform.js" async="async" defer="defer"></script>
<div id="fb-root"></div>
<script>(function (d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s);
js.id = id;
js.src = "//connect.facebook.net/en_GB/all.js#xfbml=1";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
</body>
</html>