Creating a custom WHILE loop in NANT - nant

I have tried my best to create a custom while loop but ended in vain.
Has anyone been successful in creating a custom while loop in NANT?

You can create a custom task :
<target name="sample">
<property name="foo.value" value="0"/>
<while property="foo.value" equals="0">
<do>
<echo message="${foo.value}"/>
<property name="foo.value" value="${int::parse(foo.value) + 1}"/>
</do>
</while>
</target>
<script language="C#" prefix="directory">
<code>
<![CDATA[
[TaskName("while")]
public class WhileTask : TaskContainer
{
private TaskContainer _doStuff;
private string _propertyName;
private string _equals;
private string _notEquals;
[BuildElement("do")]
public TaskContainer StuffToDo
{
get
{
return this._doStuff;
}
set
{
this._doStuff = value;
}
}
[TaskAttribute("property")]
public string PropertyName
{
get
{
return this._propertyName;
}
set
{
this._propertyName = value;
}
}
[TaskAttribute("equals")]
public string Equals
{
get
{
return this._equals;
}
set
{
this._equals = value;
}
}
[TaskAttribute("notequals")]
public string NotEquals
{
get
{
return this._notEquals;
}
set
{
this._notEquals = value;
}
}
protected override void ExecuteTask()
{
while (this.IsTrue())
{
this._doStuff.Execute();
}
}
private bool IsTrue()
{
if (!string.IsNullOrEmpty(this.Equals))
{
return this.Properties[this.PropertyName] == this.Equals;
}
return this.Properties[this.PropertyName] != this.NotEquals;
}
}
]]>
</code>
</script>

looking at the list of currently available tasks for NAnt, it looks like while is no longer supported (http://nant.sourceforge.net/release/latest/help/tasks/)
So I think the easiest and most efficient way how to do a custom while loop is recursion.
So for example, something like this:
<property name="count" value="120" />
<target name="wait">
<if test="${int::parse(count) > 0}" >
<property name="count" value="${int::parse(count) - 1}" />
<call target="wait"/>
</if>
</target>
Regards,
Marek

Here's another example of a simple yet effective version of the while loop implemented in NAnt.
<?xml version="1.0"?>
<project name="whiletask" xmlns="http://tempuri.org/nant-donotuse.xsd">
<script language="C#" prefix="loop">
<code>
<![CDATA[
/// <summary>
/// A while loop task. Will continuelly execute the task while the <c>test</c> is <c>empty</c>
/// or evalutes to <c>true</c>.
/// </summary>
[TaskName("while")]
public class WhileTask : TaskContainer
{
private string _test;
private TaskContainer _childTasks;
/// <summary>
/// The expression to test each iteration. If empty, then always evalutes to true (i.e. infinite loop.)
/// </summary>
[TaskAttribute("test", ExpandProperties = false)]
public string Test
{
get { return _test; }
set { _test = NAnt.Core.Util.StringUtils.ConvertEmptyToNull(value); }
}
/// <summary>
/// Superficial to ensure the XML schema is rendered correctly for this task. It will get executed
/// if tasks exist within it.
/// </summary>
[BuildElement("do")]
public TaskContainer ChildTasks
{
get { return _childTasks; }
set { _childTasks = value; }
}
/// <summary>
/// Executes the while loop while the <c>test</c> evalutes to true or <c>test</c> is empty.
/// </summary>
protected override void ExecuteTask()
{
while (this.Test == null
|| bool.Parse(Project.ExpandProperties(this.Test, this.Location)))
{
if (this._childTasks != null)
{
this._childTasks.Execute();
}
else
{
base.ExecuteTask();
}
}
}
}
]]>
</code>
</script>
<property name="i" value="0" />
<while test="${int::parse(i) <= 10}">
<echo message="${i}" />
<property name="i" value="${int::parse(i)+1}" />
</while>
</project>

There's quite a few ways you can do this. I wrote something similar to Cao which triggers off a property being true, so the condition can be as complex as you like and if it's made dynamic the value is evaluated each loop which is handy when you are calling functions e.g. to check a file exists. I also added simple break and continue controls. It can also be run as an infinite loop with no attributes which can be useful either when you want lots of conditions to exit (in which case use 'if' with break/continue or - in my case - I wanted to run a task until it exceptioned and then handle is with failonerror or a trycatch block.
Here's a bit of Nant script which shows two ways to countdown from 10:
<property name="greaterthanzero" value="${int::parse(count) > 0}" dynamic="true"/>
<property name="count" value="10" />
<while propertytrue="greaterthanzero" >
<echo>CountDown = ${count}</echo>
<property name="count" value="${int::parse(count) - 1}" />
</while>
<property name="count" value="10" />
<while>
<if test="${int::parse(count) > 0}" >
<echo>CountDown = ${count}</echo>
<property name="count" value="${int::parse(count) - 1}" />
<continue/>
</if>
<break/>
</while>
And here's a real world example I use to wait until a lockfile is deleted:
<property name="count" value="0" />
<property name="lockfileexists" value="${file::exists(lockfile)}" dynamic="true"/>
<while propertytrue="lockfileexists" >
<sleep seconds="1" />
<property name="count" value="${int::parse(count) + 1}" />
<if test="${count == '15'}" >
<echo>Timed out after 15 seconds</echo>
<break/>
</if>
</while>
Here's the task code:
<script language="C#" prefix="loops">
<code>
<![CDATA[
public class LoopBreakException : Exception {}
public class LoopContinueException : Exception {}
[TaskName("break")]
public class BreakTask : Task
{
protected override void ExecuteTask()
{
throw new LoopBreakException();
}
}
[TaskName("continue")]
public class ContinueTask : Task
{
protected override void ExecuteTask()
{
throw new LoopContinueException();
}
}
[TaskName("while")]
public class WhileTask : TaskContainer
{
[TaskAttribute("propertytrue")]
public string PropertyName { get; set; }
protected bool CheckCondition()
{
if (!string.IsNullOrEmpty(PropertyName))
{
try
{
return bool.Parse(Properties[PropertyName]);
}
catch (Exception ex)
{
throw new BuildException(string.Format("While Property '{0}' not found", PropertyName), Location);
}
}
//for infinite loops
return true;
}
protected override void ExecuteTask()
{
while (CheckCondition())
{
try
{
ExecuteChildTasks();
}
catch (LoopContinueException)
{
continue;
}
catch (LoopBreakException)
{
break;
}
}
}
}
]]>
</code>

Without additional information, there is a tutorial on creating a custom NAnt task here.
One nice thing about the article is the author suggests 2 means of debugging your custom task:
Copy the assembly (and pdb) file to the NAnt bin directory. Open your solution in Visual Studio that contains the source for your task. Place your breakpoints. Go to the project properties and open the Debugging page. Change the Debug Mode to Program and the Start Application to the path to the NAnt executable (e.g. C:\Program Files\NAnt\bin\NAnt.exe). Then set the working directory and/or command line arguments so that NAnt will pick up your build file. Click run and away you go.
Place System.Diagnostics.Debbugger.Break(); in your code before the line you want to break on. Re-compile the project and copy the assembly (and pdb) to the NAnt bin directory. When you run your NAnt script you should get a popup box asking you to choose a debugger.
There is another tutorial here.
Alternately, can you express your problem in terms of a foreach?

I have created the custom task by myself. But it seems there are some issues in using nested loops in NANT.
Basically I'm trying to use nested loop. A while loop inside a foreach or a foreach inside another foreach. But in both instances the loop executes the current target & the target from which the current target is called for every iteration instead of the body inside the second loop.
Regards
Sarathy

Here is one way of writing a WHILE loop in nant, with no custom tasks or script element, taking advantage of failonerror="false" on a foreach loop.
<property name="n" value="10000" /><!-- this would be inefficient if "n" is very large -->
<property name="i" value="0" />
<foreach item="String" in="${string::pad-right(' ', int::parse(n), ',')}" delim="," property="val" failonerror="false" >
<if test="${int::parse(i) > 3}"><!-- put our exit condition here -->
<fail message="condition met, exit loop early" />
</if>
<echo message="i: ${i}" />
<property name="i" value="${int::parse(i) + 1}" />
</foreach>
The output from executing the WHILE loop above is as follows. Note that, due to the failonerror="false" the fail call does not terminate the script:
[echo] i: 0
[echo] i: 1
[echo] i: 2
[echo] i: 3
[foreach] myscript.nant(24,18):
[foreach] condition met, exit loop early
BUILD SUCCEEDED - 1 non-fatal error(s), 0 warning(s)
I based the WHILE loop above on how I build a FOR loop, which is a slightly simplified version of the above code:
<property name="n" value="5" />
<property name="i" value="0" />
<foreach item="String" in="${string::pad-right(' ', int::parse(n), ',')}" delim="," property="val" >
<echo message="i: ${i}" />
<property name="i" value="${int::parse(i) + 1}" /> <!-- increment "i" -->
</foreach>
The output from the FOR loop looks like this:
[echo] i: 0
[echo] i: 1
[echo] i: 2
[echo] i: 3
[echo] i: 4
BUILD SUCCEEDED

Related

CFQL not working when a parameter is an enum

I reproduced the problem by creating a small sample with an entity called Employee and an enum called EmployeeStatus. I then created a method called LoadByStatus. The problem is the first line of the method throws an exception when the enum parameter is set to employeed. There are other things wrong as I commented out the if statement and it generated a SQL exception.
public static System.Data.IDataReader PageDataLoadByStatus(CodeFluent.Runtime.PageOptions pageOptions, DemoLoadByEnum.EmployeeStatus status)
{
if ((status == DemoLoadByEnum.EmployeeStatus.Employeed))
{
throw new System.ArgumentNullException("status");
}
CodeFluent.Runtime.CodeFluentPersistence persistence = CodeFluentContext.Get(DemoLoadByEnum.Constants.DemoLoadByEnumStoreName).Persistence;
persistence.CreateStoredProcedureCommand(null, "Employee", "LoadByStatus");
persistence.AddParameterEnumInt32("#status", status, DemoLoadByEnum.EmployeeStatus.Employeed);
if ((pageOptions != null))
{
System.Collections.IEnumerator enumerator = pageOptions.OrderByArguments.GetEnumerator();
bool b;
int index = 0;
for (b = enumerator.MoveNext(); b; b = enumerator.MoveNext())
{
CodeFluent.Runtime.OrderByArgument argument = ((CodeFluent.Runtime.OrderByArgument)(enumerator.Current));
persistence.AddParameter(string.Format("#_orderBy{0}", index), argument.Name);
persistence.AddParameter(string.Format("#_orderByDirection{0}", index), ((int)(argument.Direction)));
index = (index + 1);
}
}
System.Data.IDataReader reader = CodeFluentContext.Get(DemoLoadByEnum.Constants.DemoLoadByEnumStoreName).Persistence.ExecuteReader();
return reader;
}
Below is Employee entity
<cf:entity name="Employee" namespace="DemoLoadByEnum">
<cf:property name="Id" key="true" />
<cf:property name="Name" entityDisplay="true" />
<cf:property name="Status" typeName="{0}.EmployeeStatus" />
<cf:method name="LoadByStatus" body="LOAD(DemoLoadByEnum.EmployeeStatus status) WHERE Status = #status" />
</cf:entity>
Below is my Enum
<cf:enumeration name="EmployeeStatus" namespace="DemoLoadByEnum">
<cf:enumerationValue name="Employeed" />
<cf:enumerationValue name="Released" />
</cf:enumeration>
CodeFluent Entities has a notion of default value. Simon's answer explains this concept: https://stackoverflow.com/a/35790190/2996339
The common way to solve your problem is to add an enumeration value which act as the default value (None/Unset/Unspecified/...), or change the default value.
Another way is to set Use Persistence Default Value to False at method parameter, method or enumeration level.

unable to set/get the property values in the rowMapper

I'm trying to access the StepExecution in my RowMapper but unable to do so. I have a property set in the xml called 'prop1'. I expect this to be set but it is not setting.I also added a #BeforeStep method to the RowMapper hoping I can get the stepExecutionContext but this method is never invoked. Is there something else I need to do?
Here is my xml:
<bean id="bean1"
class="org.springframework.batch.item.database.JdbcCursorItemReader"
scope="step">
<property name="dataSource" ref="dataSource" />
<property name="sql"
value="${sql}"/>
<property name="fetchSize" value="${fetchSize}"></property>
<property name="rowMapper">
<bean id="rowMapper1" class="c.p.MyRowMapper" scope="step">
<property name="prop1" value="${prop1}"></property>
</property>
Here is my RowMapper:
public class MyRowMapper implements RowMapper<Object>{
private String prop1;
private StepExecution se;
public String getProp1() {
return stepFatpCount;
}
public void setProp1(String rop1) {
this. prop1 = prop1;
}
#BeforeStep
public void beforeStep(StepExecution stepExecution){
this.se = stepExecution;
}
}
I have some properties set in the stepExecutionContext before this step in another step and I want to use them here in the RowMapper. The same thing works in the ItemProcessor but not the RowMapper. Please let me know if I need to do something more for lazy binding or any other issue.
Thanks.
The step execution context is not shared between steps. Maybe you mean job execution context?
I suppose you could register rowMapper1 as a listener in the step (with the <listeners> tag), but if you just want to read some value from the job execution context you could use
value="#{jobExecutionContext['foobar']}"
If you do want to have injected some value from the step execution context, you just have to replace stepExecutionContext above.

Getting Data from Multiple tables in Liferay 6.0.6

i'm trying to get data from multiple tables in liferay 6.0.6 using custom sql, but for now i'm just able to display data from one table.does any one know how to do that.thanks
UPDATE:
i did found this link http://www.liferaysavvy.com/2013/02/getting-data-from-multiple-tables-in.html but for me it's not working because it gives an error BeanLocator is null,and it seems that it's a bug in liferay 6.0.6
The following technique also works with liferay 6.2-ga1.
We will consider we are in the portlet project fooproject.
Let's say you have two tables: article, and author. Here are the entities in your service.xml :
<entity name="Article" local-service="true">
<column name="id_article" type="long" primary="true" />
<column name="id_author" type="long" />
<column name="title" type="String" />
<column name="content" type="String" />
<column name="writing_date" type="Date" />
</entity>
<entity name="Author" local-service="true">
<column name="id_author" type="long" primary="true" />
<column name="full_name" type="String" />
</entity>
At that point run the service builder to generate the persistence and service layers.
You have to use custom SQL queries as described by Liferay's Documentation to fetch info from multiple databases.
Here is the code of your fooproject-portlet/src/main/ressources/default.xml :
<?xml version="1.0"?>
<custom-sql>
<sql file="custom-sql/full_article.xml" />
</custom-sql>
And the custom request in the fooproject-portlet/src/main/ressources/full_article.xml:
<?xml version="1.0"?>
<custom-sql>
<sql
id="com.myCompany.fooproject.service.persistence.ArticleFinder.findByAuthor">
<![CDATA[
SELECT
Author.full_name AS author_name
Article.title AS article_title,
Article.content AS article_content
Article.writing_date AS writing_date
FROM
fooproject_Article AS Article
INNER JOIN
fooproject_Author AS Author
ON Article.id_author=Author.id_author
WHERE
author_name LIKE ?
]]>
</sql>
</custom-sql>
As you can see, we want to fetch author's name, article's title, article's content and article's date.
So let's allow the service builder to generate a bean that can store all these informations. How ? By adding it to the service.xml ! Be careful: the fields of the bean and the fields' name returned by the query must match.
<entity name="ArticleBean">
<column name="author_name" type="String" primary="true" />
<column name="article_title" type="String" primary="true" />
<column name="article_content" type="String" />
<column name="article_date" type="Date" />
</entity>
Note: defining which field is primary here does not really matter as there will never be anything in the ArticleBean table. It is all about not having exceptions thrown by the service builder while generating the Bean.
The finder method must be implemented then. To do so, create the class com.myCompany.fooproject.service.persistence.impl.ArticleFinderImpl. Populate it with the following content:
public class ArticleFinderImpl extends BasePersistenceImpl<Article> {
}
Use the correct import statements and run the service builder. Let's make that class implement the interface generated by the service builder:
public class ArticleFinderImpl extends BasePersistenceImpl<Article> implements ArticleFinder {
}
And populate it with the actual finder implementation:
public class ArticleFinderImpl extends BasePersistenceImpl<Article> implements ArticleFinder {
// Query id according to liferay's query naming convention
public static final String FIND_BY_AUTHOR = ArticleFinder.class.getName() + ".findByAuthor";
public List<Article> findByAuthor(String author) {
Session session = null;
try {
session = openSession();
// Retrieve query
String sql = CustomSQLUtil.get(FIND_BY_AUTHOR);
SQLQuery q = session.createSQLQuery(sql);
q.setCacheable(false);
// Set the expected output type
q.addEntity("StaffBean", StaffBeanImpl.class);
// Binding arguments to query
QueryPos qpos = QueryPos.getInstance(q);
qpos.add(author);
// Fetching all elements and returning them as a list
return (List<StaffBean>) QueryUtil.list(q, getDialect(), QueryUtil.ALL_POS, QueryUtil.ALL_POS);
} catch(Exception e) {
e.printStackTrace();
} finally {
closeSession(session);
}
return null;
}
}
You can then call this method from your ArticleServiceImpl, whether it is to make a local or a remote API.
Note: it is hack. This is not a perfectly clean way to retrieve data, but it is the "less bad" you can do if you want to use Liferay's Service Builder.

How to pass data between Spring Batch jobs

I'm familiar with how to pass data between steps in a Spring Batch job. But what happens when you job is composed of many smaller jobs? In the example below, I would like to set some data in the JobExecutionContext at the end of the first job, siNotificationJob. Then, that data could be read from the JobExecutionContext of StepExecutionContext in the next job, ciNotificationJob. Do I need to promote this data somehow? I can't seem to see the results in the Job Parameter Extractor defined in step 'ciNotificationJob' that I use to configure my job parameters.
Thoughts?
Andrew
<job id="notificationJob" xmlns="http://www.springframework.org/schema/batch">
<batch:step id="pn_step_0" next="pn-step-1">
<batch:job ref="siNotificationJob" job-launcher="jobLauncher"
job-parameters-extractor="jobParamsExtractor"/>
</batch:step>
<batch:step id="pn-step-1" next="pn-step-2">
<batch:job ref="ciNotificationJob" job-launcher="jobLauncher"
job-parameters-extractor="jobParamsExtractor"/>
</batch:step>
</job>
I was able to resolve this. I'll show you through example how I solved it. It was complicated but I think the end result is fairly easy to understand.
I have one overall job called 'notificationJob'. It has three steps that calls 3 different jobs (not steps). Each of these jobs can run independently, or be called from within the top level 'notificationJob'. Also, each sub-job has many steps. I'm not going to show all those steps here, but just wanted to highlight that these are complete jobs themselves with more multliple steps.
<job id="notificationJob" xmlns="http://www.springframework.org/schema/batch">
<batch:listeners>
<batch:listener ref="pn_job-parent-listener" />
</batch:listeners>
<batch:step id="pn_step-0" next="pn-step-1">
<batch:job ref="siNotificationJob" job-launcher="jobLauncher"
job-parameters-extractor="futureSiParamsExtractor"/>
</batch:step>
<batch:step id="pn-step-1" next="pn-step-2">
<batch:job ref="ciNotificationJob" job-launcher="jobLauncher"
job-parameters-extractor="futureCiParamsExtractor"/>
</batch:step>
<batch:step id="pn-step-2">
<batch:job ref="combineResultsJob" job-launcher="jobLauncher"
job-parameters-extractor="jobParamsExtractor"/>
</batch:step>
</job>
The key is being able to extract the results from one job and read them in the next job. Now, you could do this multiple ways. One way would be to output the result from one job into a DB or text file and then have the next job read from that file/table. Since I wasn't dealing with that much data, I passed the information around in memory. So, you'll notice the job-parameter-extractors. You can either rely on a built-in implementation of a paramter extractor, or you can implement your own. I actually use both. All they do is extract the value from the StepExecution and then we'll need to promote/move them to the next sub-job.
<bean id="jobParamsExtractor" class="org.springframework.batch.core.step.job.DefaultJobParametersExtractor">
<property name="keys">
<list>
<value>OUTPUT</value>
</list>
</property>
</bean>
<bean id="futureSiParamsExtractor" class="jobs.SlideDatesParamExtractor">
<property name="mode" value="FORWARD" />
<property name="addedParams">
<map><entry>
<key><value>outputName</value></key>
<value>FUTURE_SI_JOB_RESULTS</value>
</entry></map>
</property>
</bean>
<bean id="futureCiParamsExtractor" class="jobs.SlideDatesParamExtractor">
<property name="mode" value="FORWARD" />
<property name="addedParams">
<map><entry>
<key><value>outputName</value></key>
<value>FUTURE_CI_JOB_RESULTS</value>
</entry></map>
</property>
</bean>
Finally, you'll notice that there is a parent job listener. This is the magic that transfer the state from one job and makes it available to the next. Here is my implementation of the class that does that.
<bean id="pn_job-state-listener" class="jobs.JobStateListener">
<property name="parentJobListener" ref="pn_job-parent-listener" />
</bean>
<bean id="pn_job-parent-listener" class="cjobs.ParentJobListener">
</bean>
package jobs.permnotification;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
public class ParentJobListener implements JobExecutionListener
{
private JobExecution parentExecution;
#Override
public void beforeJob(JobExecution jobExecution)
{
this.parentExecution = jobExecution;
}
#Override
public void afterJob(JobExecution jobExecution)
{
// TODO Auto-generated method stub
}
public void setParentExecution(JobExecution parentExecution)
{
this.parentExecution = parentExecution;
}
public JobExecution getParentExecution()
{
return parentExecution;
}
}
package jobs.permnotification;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
public class JobStateListener implements JobExecutionListener
{
private ParentJobListener parentJobListener;
#Override
public void beforeJob(JobExecution jobExecution)
{
if(parentJobListener == null || parentJobListener.getParentExecution() == null) return;
passStateFromParentToJob(StepKey.FUTURE_SI_JOB_RESULTS.toString(), jobExecution);
passStateFromParentToJob(StepKey.FUTURE_CI_JOB_RESULTS.toString(), jobExecution);
passStateFromParentToJob(StepKey.OUTPUT.toString(), jobExecution);
}
#Override
public void afterJob(JobExecution jobExecution)
{
if(parentJobListener == null || parentJobListener.getParentExecution() == null) return;
//take state from child step and move it into the parent execution context
passStateFromJobToParent(StepKey.FUTURE_SI_JOB_RESULTS.toString(), jobExecution);
passStateFromJobToParent(StepKey.FUTURE_CI_JOB_RESULTS.toString(), jobExecution);
passStateFromJobToParent(StepKey.OUTPUT.toString(), jobExecution);
}
private void passStateFromJobToParent(String key, JobExecution jobExecution)
{
Object obj = jobExecution.getExecutionContext().get(key);
if(obj != null)
parentJobListener.getParentExecution().getExecutionContext().put(key, obj);
}
private void passStateFromParentToJob(String key, JobExecution jobExecution)
{
Object obj = parentJobListener.getParentExecution().getExecutionContext().get(key);
if(obj != null)
jobExecution.getExecutionContext().put(key, obj);
}
public void setParentJobListener(ParentJobListener parentJobListener)
{
this.parentJobListener = parentJobListener;
}
public ParentJobListener getParentJobListener()
{
return parentJobListener;
}
}
this is sort of a hack.... recommend you use spring integration instead..but see if this applies for your situation.
if you have the spring batch meta data tables set up, you can probably get at the data that you generate within each job if you query the tables for your latest job run. All your data in the job execution context is stored and can be queried.
spring batch meta tables

Struts 2 how to display messages saved in a Interceptor which would redirec to another action?

in my interceptor, if user doesn't have enough right, there would be a warn message:
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext actionContext = invocation.getInvocationContext();
Map<String, Object> sessionMap = actionContext.getSession();
User loginUser = (User) sessionMap.get("user");
Object action = invocation.getAction();
if (loginUser != null && loginUser.getRole().getId() != Constant.AUTHORITY_ADMIN) {
((ValidationAware) action).addFieldError("user.authority",
((DefaultAction) action).getText("user.action.authority.not.enough"));
return DefaultAction.HOME_PAGE;
}
return invocation.invoke();
}
then, it would redirect to "HOME_PAGE" action, if success, display information in the jsp. So how to display the warn message?
i have used two interceptors configed in strust.xml, for admin right requirment:
<interceptor-stack name="authorityStack">
<interceptor-ref name="authority" />
<interceptor-ref name="defaultStack" />
<interceptor-ref name="store">
<param name="operationMode">STORE</param>
</interceptor-ref>
</interceptor-stack>
default is:
<interceptor-stack name="default">
<interceptor-ref name="login" />
<interceptor-ref name="defaultStack" />
<interceptor-ref name="store">
<param name="operationMode">AUTOMATIC</param>
</interceptor-ref>
</interceptor-stack>
Here's how I handle access control in Struts2. It's really easy and quite re-usable:
First, create an interface called SecurityCheckAware.
public interface SecurityCheckAware {
void checkRight();
}
Then, create an interceptor called SecurityCheckInterceptor.
public class SecurityCheckInterceptor extends AbstractInterceptor {
#Override
public String intercept(final ActionInvocation invocation) throws Exception {
if (invocation.getAction() instanceof SecurityCheckAware) {
SecurityCheckAware action = (SecurityCheckAware) invocation.getAction();
action.checkRight();
}
return invocation.invoke();
}
}
Then, define the interceptor in your stack.
Any action that you want to perform security checks in should implement SecurityCheckAware. For example:
#Override
public void checkRight() {
User loginUser = (User) session.get("user");
if (loginUser != null && loginUser.getRole().getId() != Constant.AUTHORITY_ADMIN) {
throw new AccessViolation("You do not have permission to access this page.");
}
}
Next, create a custom exception that extends RuntimeException (or some subclass thereof). I call it AccessViolation.
Lastly, map AccessViolation to an error page in your struts.xml, such as:
<global-results>
<result name="accessDenied">/WEB-INF/jsp/accessDenied.jsp</result>
</global-results>
<global-exception-mappings>
<exception-mapping exception="com.example.AccessViolation" result="accessDenied"/>
</global-exception-mappings>
Note: You can fore-go the SecurityCheckAware and SecurityCheckInterceptor and just use the existing Preparable and PrepareInterceptor, but I like being able to encapsulate my security checks in their own method.
This doesn't rely on redirection or action/field errors (as in your question), but it should deliver everything you're looking for.
I use MessageStoreInterceptor and it's easy.
MessageStoreInterceptor - http://struts.apache.org/release/2.3.x/docs/message-store-interceptor.html
Let me know if you need more help.