CustomMenu

Thursday, May 29, 2014

Wing Comparison in the 80 Day RUT Iron Condor

In the last post, 80 DTE RUT IC - 2013 / 2014 Performance, we looked at some of the statistics for the 80 DTE RUT "no touch" Iron Condor (IC).  We noticed that the trade has not performed as well in the last year and a half as it did in the prior six year period (through 2012).

Another way to look at the performance of this IC, is to review the performance of its put credit spreads and call credit spreads independently.   The heat graphs below show the performance of the different delta versions of the strategy, with the performance of the call credit spreads shown separately from the performance of the put credit spreads for the same expiration.

With the no touch 8 delta version, the call spreads lost 11 times since 2007, while the put spreads lost 6 times.  The percentages are 13% and 7% respectively.  We can also compare the sum of all the losing percentages for the call spreads and put spreads separately.  For the 8 delta version, the sum of the negative returns for the calls was -287% versus -302% for the puts.  Even though the puts lost less often, the magnitude of those losing trades was larger.  The average loss for the losing call spreads was -26%, while the average loss for the losing put spreads was -50%.



For the 12 delta version, the calls lost 15 times, while the puts lost 10 times.  18% and 12% respectively.  The sum of the negative returns for the calls was -568% versus -430% for the puts.  The calls contributed more to the total loses than the puts, and the call loses occurred more frequently.  The average loss for the losing call spreads was -38%, while the average loss for the losing put spreads was -43%.



For the 16 delta version, the calls lost 17 times, while the puts lost 11 times.  20% and 13% respectively.  The sum of the negative returns for the calls was -862% versus -613% for the puts.  Again, the calls contributed more to the total loses than the puts, and the call loses occurred more frequently. The average loss for the losing call spreads was -51%, while the average loss for the losing put spreads was -56%.



For the 20 delta version, the calls lost 22 times, while the puts lost 12 times.  26% and 14% respectively.  The sum of the negative returns for the calls was -1137% versus -738% for the puts.  The calls again contributed more to the total loses than the puts, and the call loses occurred more frequently.  The average loss for the losing call spreads was -52%, while the average loss for the losing put spreads was -62%.


The call spreads lost nearly twice as often as the put spreads, but the magnitude of the average loss for each put side loss was larger.  The total losses on the call side were larger than the total losses on the put side though, except for the 8 delta variation.  It's worth noticing where most of the losses have been occurring in these trades in the last year and a half...the call spreads.  These are all important points to consider while we continue to dig into iron condors on the RUT.

Tuesday, May 27, 2014

80 DTE RUT IC - 2013 / 2014 Performance

During the last few weeks, I've shown the results for four different variations of "no touch" RUT iron condors (ICs) at six different days-to-expiration (DTE) periods:
As they are, many traders would not trade these "no touch" trades...although a couple may.  When considering whether to trade any strategy, it is important to look at how a strategy is performing today versus the past.  Let's take a look at a few pieces of information to help us evaluate the 80 DTE RUT "no touch" IC.

We will start by comparing the Summary Statistics of this strategy for the 2013 - 2014 period with the same statistics from the 2007 - 2014 and 2007 - 2012 periods.



 

All of the statistics for the last year and a half are worse than the statistics from the entire test range (2007 - 2014) as well as the prior test range (2007 - 2012).  The point is made a bit more clear by looking at all three periods graphically.  The bars in green below represent the statistics from the period 2013 - 2014.

We see a negative AGR during the last year and a half from the 80 DTE RUC "no touch" IC, for all delta variations.

We see a smaller "best trade" during this recent period, for all delta variations.

A smaller win rate currently, versus the past.

And a larger standard deviation of returns currently, versus the past.  The larger the short strike delta, the greater the standard deviation of returns.

We will look at some other aspects of this 80 DTE strategy in the next post.

Friday, May 23, 2014

Iron Condor Backtest - RUT - 24 DTE

In this post we will look at the automated backtesting results for four variations of a 24 days-to-expiration (DTE) iron condor (IC).  As with the four prior tests, the short strikes for both the call credit spreads and put credit spreads will be at approximately the same delta.  See my post Thoughts on Options Strategy Backtests for some background on my testing approach.  The prior IC backtesting results posts can be found at:
As with the other tests, in these backtests there will be no adjustments during the trades and no hedging to start the positions leaning one direction or another.  Many of these settings are available in the backtester, but we will continue to look at this basic IC case.  Here are the setup details:


(1) The backtester will start looking for trades that meet the entry DTE requirement after this date
(2) The backtester will not take any trades that will have an exit DTE after this date
(3) Some trading platforms call 24 DTE, 22 DTE (e.g., TOS).  The OSB uses a DTE based on the number of days to the expiration date in the option code/opra code, which is a Saturday for indexes
(4) Some trading platforms call 8 DTE, 6 DTE (e.g., TOS)
(5) Russell 2000 Index options
(6) Four 24 DTE "no-touch" iron condors will be tested with their short strikes at varying deltas (8, 12, 16, and 20)
(7) The distance between the short call and long call (also, the distance between the short and long puts)

The equity curves for each of the four variations are shown in the graph below.  In addition to the equity curves, the ATM IV at trade initiation is also plotted (the average of the ATM call IV and ATM put IV).  Please note that the dates in the chart are expiration dates.

If I pick a random expiration date, for example 12/19/2009, we can see that when the trade was initiated, the ATM IV was 25.  When the trade was closed, the cumulative non-compounded profit had grown to between $11.7k and $44.4k depending on the short delta of the variation of the strategy.


For these trades, the maximum reg-t margin requirement is between $17.0k and $18.8k assuming your brokerage only margins one side of your IC.  If you have portfolio margin, your requirement is somewhere around one-quarter to one-half the reg-t margin numbers.  The 8 delta trade had the highest margin requirement, while the 20 delta trade had the lowest margin requirement.  The margin difference is related to the difference in the size of the credits received.  Higher delta equals larger credit; lower delta equals smaller credit.

In the table below are the standard trade metrics for the four ICs with different short strikes (8 delta, 12 delta, 16 delta, and 20 delta).


Similar to the other "no touch" trades tested, there are some big drawdowns in these trades because of the lack of adjustments and hedging.  The number of winning trades increases as the size of the short deltas decreases..the further away from ATM, the higher the win rate.  The combination of shorter DTE and closeness of the short strikes makes the higher delta condors have a lower win rate.  On the other hand, the higher credit received for the higher delta strategies' trades does result in the higher delta strategies having a higher AGR.

In the heat maps, we can see the performance by expiration month of each of the individual trades of each of the four strategies.  The 0% cells represent expiration months were no trade was initiated.  Some of these 0% cells are due to lack of data or bad prints on the trade entry day...leading the backtester to skip that month for testing.


We can also look at the 8 delta strategy in terms of the P&L range (in %), range of the underlying (in %), and IV range (in %), for each trade by expiration month.  This data is shown in the graphs below.  These graphs are showing the open, which is the 0% level, the high (green bar), the low (red bar), and the value at trade close (the blue line).  Also, note that the months shown on the horizontal axis are not displaying expiration dates in order to make the charts less cluttered with axis labels.

The top graph displays P&L range for the 8 delta variation of the 24 DTE "no touch" IC.  Even with the short duration of these trades, you can see that when a trade closes profitably the range is mostly on the positive side of the graph.  With losing trades being exactly the opposite.

The middle graph shows the range of the underlying, the RUT, in percent terms during the life of each trade.  The bullish bias of the US markets is still evident in this shorter duration trade, although not as obvious as with the longer duration trades (e.g. 80 DTE IC).

The bottom graph shows the ATM IV range and closing value in percent terms, during the life of each IC trade.  IV decays to zero as we approach expiration, so you would expect these bars to be mostly red, with the closing value to be negative (the blue line).  There were several times where ATM IV finished higher, and green...usually when there were market drops...as expected.



The next three graphs show the P&L range for the 12 delta, 16 delta, and 20 delta variations of this 24 DTE "no touch" IC strategy.  You can see the increase in P&L volatility as the delta increase from 12 to 20.



The 24 DTE variation will be the last in this series on "no touch" RUT ICs at different DTE.  In the next few posts we will review some additional details related to these "no touch" RUT ICs.  After those posts, will move onto the SPX, NDX and VIX "no touch" ICs.  Drop me a note if you'd like to see different data presented, and I'll do my best to incorporate your suggestions in the next post or two.


Tuesday, May 20, 2014

Iron Condor Backtest - RUT - 31 DTE

In this post we will look at the automated backtesting results for four variations of a 31 days-to-expiration (DTE) iron condor (IC).  As with the four prior tests, the short strikes for both the call credit spreads and put credit spreads will be at approximately the same delta.  See my post Thoughts on Options Strategy Backtests for some background on my testing approach.  The prior IC backtesting results posts can be found at:
As with the other tests, in these backtests there will be no adjustments during the trades and no hedging to start the positions leaning one direction or another.  Many of these settings are available in the backtester, but we will continue to look at this basic IC case.  Here are the setup details:


(1) The backtester will start looking for trades that meet the entry DTE requirement after this date
(2) The backtester will not take any trades that will have an exit DTE after this date
(3) Some trading platforms call 31 DTE, 29 DTE (e.g., TOS).  The OSB uses a DTE based on the number of days to the expiration date in the option code/opra code, which is a Saturday for indexes
(4) Some trading platforms call 8 DTE, 6 DTE (e.g., TOS)
(5) Russell 2000 Index options
(6) Four 31 DTE "no-touch" iron condors will be tested with their short strikes at varying deltas (8, 12, 16, and 20)
(7) The distance between the short call and long call (also, the distance between the short and long puts)

The equity curves for each of the four variations are shown in the graph below.  In addition to the equity curves, the ATM IV at trade initiation is also plotted (the average of the ATM call IV and ATM put IV).  Please note that the dates in the chart are expiration dates.

If I pick a random expiration date, for example 03/22/2008, we can see that when the trade was initiated, the ATM IV was 28.  When the trade was closed, the cumulative non-compounded profit had grown to between $14.8k and $25.2k depending on the short delta of the variation of the strategy.


For these trades, the maximum reg-t margin requirement is between $16.6k and $19.2k assuming your brokerage only margins one side of your IC.  If you have portfolio margin, your requirement is somewhere around one-quarter to one-half the reg-t margin numbers.  The 8 delta trade had the highest margin requirement, while the 20 delta trade had the lowest margin requirement.  The margin difference is related to the difference in the size of the credits received.  Higher delta equals larger credit; lower delta equals smaller credit.

In the table below are the standard trade metrics for the four ICs with different short strikes (8 delta, 12 delta, 16 delta, and 20 delta).


Similar to the other "no touch" trades tested, there are some big drawdowns in these trades because of the lack of adjustments and hedging.  The number of losing trades decreases with the size of the short deltas..the further away from ATM, the higher the win rate.  The combination of shorter DTE and closeness of the short strikes makes these higher delta condors have a lower win rate.  On the other hand, the higher credit received for the higher delta strategies' trades does result in the higher delta strategies having a higher AGR.

In the heat maps, we can see the performance by expiration month of each of the individual trades of each of the four strategies.  The 0% cells represent expiration months were no trade was initiated.  Some of these 0% cells are due to lack of data or bad prints on the trade entry day...leading the backtester to skip that month for testing.

We can also look at the 8 delta strategy in terms of the P&L range (in %), range of the underlying (in %), and IV range (in %), for each trade by expiration month.  This data is shown in the graphs below.  These graphs are showing the open, which is the 0% level, the high (green bar), the low (red bar), and the value at trade close (the blue line).  Also, note that the months shown on the horizontal axis are not displaying expiration dates in order to make the charts less cluttered with axis labels.

The top graph displays P&L range for the 8 delta variation of the 31 DTE "no touch" IC.  You can see that when a trade closes profitably, the range is mostly on the positive side of the graph, with losing trades being exactly the opposite.

The middle graph shows the range of the underlying, the RUT, in percent terms during the life of each trade.  The bullish bias of the US markets is still evident in this shorter duration trade, although not as obvious as with the longer duration (e.g. 80 DTE IC).

The bottom graph shows the ATM IV range and closing value in percent terms, during the life of each IC trade.  IV decays to zero as we approach expiration, so you would expect these bars to be mostly red, with the closing value to be negative (the blue line).  There were several times where ATM IV finished higher, and green...usually when there were market drops...as expected.




The next three graphs show the P&L range for the 12 delta, 16 delta, and 20 delta variations of this 31 DTE "no touch" IC strategy.  You can see the increase in P&L volatility as the delta increase from 12 to 20.



In the next backtesting post I will look at this same IC entered at 24 DTE, one week later than this test.  The 24 DTE variation will be the last in this series on "no touch" RUT ICs.  We will look at the SPX and NDX "no touch" ICs after the 24 DTE RUT IC.  Drop me a note if you'd like to see different data presented, and I'll do my best to incorporate your suggestions in the next post or two.


Friday, May 16, 2014

Iron Condor Backtest - RUT - 38 DTE

In this post we will look at the automated backtesting results for four variations of a 38 days-to-expiration (DTE) iron condor (IC).  As with the three prior tests, the short strikes for both the call credit spreads and put credit spreads will be at approximately the same delta.  See my post Thoughts on Options Strategy Backtests for some background on my testing approach.  The prior IC backtesting results posts can be found at:
As with the other tests, in these backtests there will be no adjustments during the trades and no hedging to start the positions leaning one direction or another.  Many of these settings are available in the backtester, but we will continue to look at this basic IC case.  Here are the setup details:


(1) The backtester will start looking for trades that meet the entry DTE requirement after this date
(2) The backtester will not take any trades that will have an exit DTE after this date
(3) Some trading platforms call 38 DTE, 36 DTE (e.g., TOS).  The OSB uses a DTE based on the number of days to the expiration date in the option code/opra code, which is a Saturday for indexes
(4) Some trading platforms call 8 DTE, 6 DTE (e.g., TOS)
(5) Russell 2000 Index options
(6) Four 38 DTE "no-touch" iron condors will be tested with their short strikes at varying deltas (8, 12, 16, and 20)
(7) The distance between the short call and long call (also, the distance between the short and long puts)

The equity curves for each of the four variations are shown in the graph below.  In addition to the equity curves, the ATM IV at trade initiation is also plotted (the average of the ATM call IV and ATM put IV).  Please note that the dates in the chart are expiration dates.

If I pick a random expiration date, for example 03/22/2008, we can see that when the trade was initiated, the ATM IV was 28.  When the trade was closed, the cumulative non-compounded profit had grown to between $19.7k and $24.4k depending on the short delta of the variation of the strategy.


For these trades, the maximum reg-t margin requirement is between $16.3k and $18.7k assuming your brokerage only margin's one side of your IC.  If you have portfolio margin, your requirement is somewhere around one-quarter to one-half the reg-t margin numbers.

In the table below are the standard trade metrics for the four ICs with different short strikes (8 delta, 12 delta, 16 delta, and 20 delta).


Similar to the other "no touch" trades tested, there are some big drawdowns in these trades because of the lack of adjustments and hedging.  The number of losing trades decreases with the size of the short deltas..the further away from ATM, the higher the win rate.  The combination of shorter DTE and closeness of the short strikes makes these higher delta condors have a lower win rate.  On the other hand, the higher credit received for the higher delta strategies' trades does result in the higher delta strategies having a higher AGR.

In the heat maps, we can see the performance by expiration month of each of the individual trades of each of the four strategies.  The 0% cells represent expiration months were no trade was initiated.  Some of these 0% cells are due to lack of data or bad prints on the trade entry day...leading the backtester to skip that month for testing.


We can also look at the 8 delta strategy in terms of the P&L range (in %), range of the underlying (in %), and IV range (in %), for each trade by expiration month.  This data is shown in the graphs below.  These graphs are showing the open, which is the 0% level, the high (green bar), the low (red bar), and the value at trade close (the blue line).  Also, note that the months shown on the horizontal axis are not displaying expiration dates in order to make the charts less cluttered with axis labels.

The top graph displays P&L range for the 8 delta variation of the 38 DTE "no touch" IC.  You can see that when a trade closes profitably, the range is mostly on the positive side of the graph, with losing trades being exactly the opposite.

The middle graph shows the range of the underlying, the RUT, in percent terms during the life of each trade.  The bullish bias of the US markets is still evident in this shorter duration trade, although not as obvious as with the longer duration (e.g. 80 DTE IC).

The bottom graph shows the ATM IV range and closing value in percent terms, during the life of each IC trade.  IV decays to zero as we approach expiration, so you would expect these bars to be mostly red, with the closing value to be negative (the blue line).  There were several times where ATM IV finished higher, and green...usually when there were market drops...as expected.





The next three graphs show the P&L range for the 12 delta, 16 delta, and 20 delta variations of this 38 DTE "no touch" IC strategy.  You can see the increase in P&L volatility as the delta increase from 12 to 20.



In the next backtesting post I will look at this same IC entered at 31 DTE, one week later than this test.  Drop me a note if you'd like to see different data presented, and I'll do my best to incorporate your suggestions in the next post or two.


Thursday, May 15, 2014

Spring Thymeleaf Permissions CRUD - Part 2

This post builds on the code from part 1 of this series,Spring Thymeleaf Permissions CRUD - Part 1.  In the past post we updated the security configuration, created the Permission DTO and Permission controller.  In this post we will finish off the CRUD functionality for the Permission entity by creating the three Thymeleaf CRUD pages.


1. We will start by creating the list page, as we have done with all of the prior entity CRUD pages.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <!-- Core CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/bootstrap-3.1.1.min.css" 
        data-th-href="@{/resources/css/bootstrap-3.1.1.min.css}" />
        
    <link type="text/css" rel="stylesheet" href="../../resources/font-awesome/css/font-awesome.css" 
        data-th-href="@{/resources/font-awesome/css/font-awesome.css}" />

    <!-- SB Admin CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/sb-admin.css" 
        data-th-href="@{/resources/css/sb-admin.css}" />
    
    <style>
        .no-border-on-me>thead>tr>th,
        .no-border-on-me>tbody>tr>th,
        .no-border-on-me>tfoot>tr>th,
        .no-border-on-me>thead>tr>td,
        .no-border-on-me>tbody>tr>td,
        .no-border-on-me>tfoot>tr>td
        {
            border-top-style: none;
            border-bottom-style: none;
        }
    </style>
    <style>
        .dropdown-menu {
          min-width: 0px;
        }    
    </style>

    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
          <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
          <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
        <![endif]-->

    <title data-th-text="#{permission.list.page.title}">Title</title>
</head>

<body>

<div id="wrapper">                <!-- /#wrapper -->

    <div data-th-replace="fragments/sb-admin :: top-nav"></div>
    
    <div data-th-replace="fragments/sb-admin :: vert-nav"></div>

    <div id="page-wrapper">
        <div class="row">
            <div class="col-xs-12">
            
                <h4 class="page-header" data-th-text="#{permission.list.table.title}">Configured Users</h4>
                <div class="table responsive">
                <table class="table table-striped table-bordered table-hover">
                    <thead>
                        <tr>
                            <th class="col-xs-1" data-th-text="#{permission.list.id.label}">Id</th>
                            <th class="col-xs-4" data-th-text="#{permission.list.permission.label}">PermissionName</th>
                            <th class="col-xs-1" data-th-each="role : ${allRoles}">
                                 <span data-th-text="${role.rolename}"></span>
                            </th>
                            <th class="col-xs-1" data-th-text="#{permission.list.actions.label}">Action</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr data-th-each="permission : ${permissions}">
                            <td data-th-text="${permission.id}">1</td>
                            <td data-th-text="${permission.permissionname}">perm1</td>
                            <td th:if="${allRoles[0]}">
                                <input th:if="${#lists.contains(permission.permRoles, allRoles[0])}" type="checkbox" 
                                    name="option1" checked="checked" disabled="disabled"></input>
                            </td>
                            <td th:if="${#lists.size(allRoles)} > 1">
                                <input th:if="${#lists.contains(permission.permRoles, allRoles[1])}" type="checkbox" 
                                    name="option1" checked="checked" disabled="disabled"></input>
                            </td>
                            <td th:if="${#lists.size(allRoles)} > 2">
                                <input th:if="${#lists.contains(permission.permRoles, allRoles[2])}" type="checkbox" 
                                    name="option1" checked="checked" disabled="disabled"></input>
                            </td>
                            <td th:if="${#lists.size(allRoles)} > 3">
                                <input th:if="${#lists.contains(permission.permRoles, allRoles[3])}" type="checkbox" 
                                    name="option1" checked="checked" disabled="disabled"></input>
                            </td>
                            <td th:if="${#lists.size(allRoles)} > 4">
                                <input th:if="${#lists.contains(permission.permRoles, allRoles[4])}" type="checkbox" 
                                    name="option1" checked="checked" disabled="disabled"></input>
                            </td>
                            <td th:if="${#lists.size(allRoles)} > 5">
                                <input th:if="${#lists.contains(permission.permRoles, allRoles[5])}" type="checkbox" 
                                    name="option1" checked="checked" disabled="disabled"></input>
                            </td>
                            <td>
                                <div class="btn-group">
                                  <button class="btn btn-warning btn-xs dropdown-toggle" 
                                        type="button" data-toggle="dropdown" data-th-text="#{permission.list.actions.label}">
                                    Actions<span class="caret"></span>
                                  </button>
                                  <ul class="dropdown-menu">
                                    <li>
                                        <a href="#" data-th-href="@{/permission/edit(id=${permission.id})}">
                                            <span class="glyphicon glyphicon-pencil"></span>&nbsp;&nbsp;
                                            <span data-th-text="#{button.label.edit}">edit</span>
                                        </a>
                                    </li>
                                    <li>
                                        <a href="#" data-th-href="@{/permission/delete(id=${permission.id},phase=stage)}">
                                           <span class="glyphicon glyphicon-trash"></span>&nbsp;&nbsp;
                                           <span data-th-text="#{button.label.delete}">delete</span>
                                        </a>
                                    </li>
                                  </ul>
                                </div>                          
                            </td>
                        </tr>
                        <tr data-th-remove="all">
                            <td>2</td>
                            <td>perm_test</td>
                            <td>role1</td>
                            <td>
                                <div class="btn-group">
                                  <button class="btn btn-warning btn-xs dropdown-toggle" type="button" data-toggle="dropdown" 
                                        data-th-text="#{permission.list.actions.label}">
                                    Actions<span class="caret"></span>
                                  </button>
                                  <ul class="dropdown-menu">
                                    <li>
                                        <a href="#">
                                            <span class="glyphicon glyphicon-pencil"></span>&nbsp;&nbsp;
                                            <span data-th-text="#{button.label.edit}">edit</span>
                                        </a>
                                    </li>
                                    <li>
                                        <a href="#">
                                           <span class="glyphicon glyphicon-trash"></span>&nbsp;&nbsp;
                                           <span data-th-text="#{button.label.delete}">delete</span>
                                        </a>
                                    </li>
                                  </ul>
                                </div>                          
                            </td>
                        </tr>
                    </tbody>
                </table>
                </div>
                <form class="form" action="#" data-th-action="@{/permission/add}" data-th-object="${permissionDTO}" method="post">
                <div class="table responsive">
                    <table class="no-border-on-me table ">
                        <thead>
                            <tr>
                                <th class="col-xs-2"></th>
                                <th class="col-xs-4" data-th-text="#{permission.list.permission.label}">Perm</th>
                                <th class="col-xs-6" data-th-text="#{permission.list.actions.label}">Action</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr>
                                <td><input type="text" hidden="hidden" data-th-field="*{id}"></input></td>
                                <td><input class="form-control" type="text" data-th-field="*{permissionname}" placeholder="Permission Name"></input></td>
                                <td>
                                    <button type="submit" class="btn btn-primary" data-th-text="#{button.label.add}">Add Permission</button>
                                </td>
                            </tr>
                            <tr>
                                <td class="col-xs-2"></td>
                                <td class="col-xs-4 text-danger" data-th-if="${#fields.hasErrors('permissionname')}" data-th-errors="*{permissionname}">permission error</td>
                                <td class="col-xs-6"></td>
                            </tr>
                        </tbody>
                    </table>
                </div>
                </form>
                 <div class="alert alert-danger alert-dismissable" th:if="${error != null}">
                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
                    <h4 data-th-text="${error}">Error!</h4>
                </div>
                <div class="alert alert-success alert-dismissable" th:if="${message != null}">
                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
                    <h4 data-th-text="${message}">Success!</h4>
                </div>              
                
            </div>  <!-- /.col-lg-12 -->                
        </div>      <!-- /.row -->              
    </div>          <!-- page wrapper -->
</div>              <!-- /#wrapper -->

        
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script type="text/javascript" src="../../resources/js/jquery-1.11.0.min.js" 
        data-th-src="@{/resources/js/jquery-1.11.0.min.js}"></script>
        
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script type="text/javascript" src="../../resources/js/bootstrap-3.1.1.min.js" 
        data-th-src="@{/resources/js/bootstrap-3.1.1.min.js}"></script>

    <!-- Core Scripts - Include with every page -->
    <script type="text/javascript" src="../../resources/js/plugins/metisMenu/jquery.metisMenu.js" 
        data-th-src="@{/resources/js/plugins/metisMenu/jquery.metisMenu.js}"></script>
        
    <!-- SB Admin Scripts - Include with every page -->
    <script type="text/javascript" src="../../resources/js/sb-admin.js" 
        data-th-src="@{/resources/js/sb-admin.js}"></script>

</body>
</html>
There are several lines highlighted in the code above.  Here is a bit more detail about those highlighted lines:
  • Lines 68 - 70: In this block of code we are displaying all of the roles as column headers.  This section should be limited to 6 roles as the detail below can only display six roles.  From a functional perspective, I didn't plan on having more than three roles, with a possibility for a fourth.  So, I figured a maximum of six in the display was more than enough.  This header should really have a cap of six too...so this is a refactoring opportunity for the future.
  • Lines 78, 82, 86, 90, 94, 98: First, these columns (td tags) will only be displayed if there is a header/role for this particular column.  If there are only three roles in the system, then only three role headers will print, resulting in three role columns.  These lines will be conditionally displayed as we loop through each of the permissions in the permissions table.  
  • Lines 78, 82, 86, 90, 94, 98: If the permission being displayed is associated with a role in the column (td tag), then a disabled check box will be displayed, otherwise nothing will be displayed in that column.  Each td block corresponds to a column, and each column corresponds to a specific role.  If you spend some time looking at this code, the functionality should become clear...if not, please leave a comment and I will try to elaborate.
  • Starting on line 155 is the form used to add a permission to the system.  This line also refers to the DTO used for adding the permission.  The line referring to the name of the permission is line number 168.


2. Next we move onto the Permission edit page.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <!-- Core CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/bootstrap-3.1.1.min.css" 
        data-th-href="@{/resources/css/bootstrap-3.1.1.min.css}" />
        
    <link type="text/css" rel="stylesheet" href="../../resources/font-awesome/css/font-awesome.css" 
        data-th-href="@{/resources/font-awesome/css/font-awesome.css}" />

    <!-- SB Admin CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/sb-admin.css" 
        data-th-href="@{/resources/css/sb-admin.css}" />
    
    <style>
        .no-border-on-me>thead>tr>th,
        .no-border-on-me>tbody>tr>th,
        .no-border-on-me>tfoot>tr>th,
        .no-border-on-me>thead>tr>td,
        .no-border-on-me>tbody>tr>td,
        .no-border-on-me>tfoot>tr>td
        {
            border-top-style: none;
            border-bottom-style: none;
        }
    </style>

    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
          <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
          <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
        <![endif]-->

    <title data-th-text="#{permission.edit.page.title}">Title</title>
</head>

<body>

<div id="wrapper">                <!-- /#wrapper -->

    <div data-th-replace="fragments/sb-admin :: top-nav"></div>
    
    <div data-th-replace="fragments/sb-admin :: vert-nav"></div>

    <div id="page-wrapper">
        <div class="row">
            <div class="col-lg-12">

                <h4 class="page-header" data-th-text="#{permission.edit.head.title}">Edit Permission</h4>
                <div class="col-sm-2"></div>
                <div class="col-sm-6">
                    <form class="form-horizontal" action="#" data-th-action="@{/permission/edit}" data-th-object="${permissionDTO}" method="post">
                        <div class="form-group">
                            <label class="col-sm-5 control-label" data-th-text="#{permission.list.permission.label}">Permission</label>
                            <div class="col-sm-7">
                                <input type="text" hidden="hidden" data-th-value="*{id}" data-th-field="*{id}" ></input>
                                <input type="text" class="form-control" data-th-value="*{permissionname}" data-th-field="*{permissionname}" ></input>
                            </div>
                        </div>
                        <div class="form-group" th:each="role : ${allRoles} ">
                            <label class="col-sm-5 control-label" data-th-text="${role.rolename}">Role 1</label>
                            <div class="col-sm-7">
                                <input type="checkbox" th:field="*{permRoles}" th:value="${role.id}"></input>
                            </div>
                        </div>
                        <div class="form-group">
                            <div class="col-sm-offset-5 col-sm-7" >
                                <button type="submit" class="btn btn-primary"        name="action" 
                                        data-th-value="#{button.action.save}"   data-th-text="#{button.label.save}"  >Save</button>
                                <button type="submit" class="btn btn-default active" name="action" 
                                        data-th-value="#{button.action.cancel}" data-th-text="#{button.label.cancel}">Cancel</button>
                            </div>
                        </div>
                        <div class="form-group">
                            <div class="col-sm-offset-5 col-sm-7" >
                                <p class="text-danger" data-th-if="${#fields.hasErrors('permissionname')}" 
                                        data-th-errors="*{permissionname}">type error</p> 
                            </div>
                        </div>
                    </form>
                </div>
                <div class="col-sm-4"></div>
                    
            </div>  <!-- /.col-lg-12 -->                
        </div>      <!-- /.row -->              
    </div>          <!-- page wrapper -->
</div>              <!-- /#wrapper -->

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script type="text/javascript" src="../../resources/js/jquery-1.11.0.min.js" 
        data-th-src="@{/resources/js/jquery-1.11.0.min.js}"></script>
        
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script type="text/javascript" src="../../resources/js/bootstrap-3.1.1.min.js" 
        data-th-src="@{/resources/js/bootstrap-3.1.1.min.js}"></script>

    <!-- Core Scripts - Include with every page -->
    <script type="text/javascript" src="../../resources/js/plugins/metisMenu/jquery.metisMenu.js" 
        data-th-src="@{/resources/js/plugins/metisMenu/jquery.metisMenu.js}"></script>
        
    <!-- SB Admin Scripts - Include with every page -->
    <script type="text/javascript" src="../../resources/js/sb-admin.js" 
        data-th-src="@{/resources/js/sb-admin.js}"></script>

</body>
</html>
Note the section highlighted in blue above. Recall that besides the DTO object passed to this page as part of the HTTP GET request, the list of all roles in the system is also passed. In the highlighted rows above, all of the roles in the system are displayed as part of the for-each loop. For each of these roles, a check box is displayed. If the role is associated with the permission being edited, then a check is placed in the box, otherwise the checkbox is left empty.

The other code in this edit page should look similar to all of the other edit pages we have created on this blog.


3. Lastly, we have the Permission delete page.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <!-- Core CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/bootstrap-3.1.1.min.css" 
        data-th-href="@{/resources/css/bootstrap-3.1.1.min.css}" />
        
    <link type="text/css" rel="stylesheet" href="../../resources/font-awesome/css/font-awesome.css" 
        data-th-href="@{/resources/font-awesome/css/font-awesome.css}" />

    <!-- SB Admin CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/sb-admin.css" 
        data-th-href="@{/resources/css/sb-admin.css}" />
    
    <style>
        .no-border-on-me>thead>tr>th,
        .no-border-on-me>tbody>tr>th,
        .no-border-on-me>tfoot>tr>th,
        .no-border-on-me>thead>tr>td,
        .no-border-on-me>tbody>tr>td,
        .no-border-on-me>tfoot>tr>td
        {
            border-top-style: none;
            border-bottom-style: none;
        }
    </style>

    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
          <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
          <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
        <![endif]-->

    <title data-th-text="#{permission.delete.page.title}">Title</title>
</head>

<body>

<div id="wrapper">                <!-- /#wrapper -->

    <div data-th-replace="fragments/sb-admin :: top-nav"></div>
    
    <div data-th-replace="fragments/sb-admin :: vert-nav"></div>

    <div id="page-wrapper">
        <div class="row">
            <div class="col-lg-12">

                <h4 class="page-header" data-th-text="#{permission.delete.head.title}">Delete Strategy</h4> 
                <div class="col-sm-2"></div>
                <div class="col-sm-6">
                    <form class="form-horizontal" action="#" method="get">
                        <div class="form-group">
                            <label class="col-sm-5 control-label" data-th-text="#{permission.list.id.label}">Permission Id</label>
                            <div class="col-sm-7">
                                <input type="text" class="form-control" data-th-value="${permission.id}" disabled="disabled" ></input>
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-5 control-label" data-th-text="#{permission.list.permission.label}">Permission Name</label>
                            <div class="col-sm-7">
                                <input type="text" class="form-control" data-th-value="${permission.permissionname}" disabled="disabled" ></input>
                            </div>
                        </div>
                        <div class="form-group" th:each="role : ${permission.permRoles} ">
                            <label class="col-sm-5 control-label" data-th-text="${role.rolename}">Role 1</label>
                            <div class="col-sm-7">
                                <input type="checkbox" name="option1" checked="checked" disabled="disabled"></input>
                            </div>
                        </div>
                    </form>
                    
                    <div class="form-horizontal">
                        <div class="form-group">
                            <label class="col-sm-5 control-label"></label>
                            <div class="col-sm-7" >
                                <a href="#" data-th-href="@{/permission/delete(id=${permission.id},phase=#{button.action.delete} )}">
                                <button type="button" class="btn btn-primary" data-th-text="#{button.label.delete}">Delete</button></a>
                                
                                <a href="#" data-th-href="@{/permission/delete(id=${permission.id},phase=#{button.action.cancel} )}">
                                <button type="button" class="btn btn-default active" data-th-text="#{button.label.cancel}">Cancel</button></a>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="col-sm-4"></div>

            </div>  <!-- /.col-lg-12 -->                
        </div>      <!-- /.row -->              
    </div>          <!-- page wrapper -->
</div>              <!-- /#wrapper -->

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script type="text/javascript" src="../../resources/js/jquery-1.11.0.min.js" 
        data-th-src="@{/resources/js/jquery-1.11.0.min.js}"></script>
        
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script type="text/javascript" src="../../resources/js/bootstrap-3.1.1.min.js" 
        data-th-src="@{/resources/js/bootstrap-3.1.1.min.js}"></script>

    <!-- Core Scripts - Include with every page -->
    <script type="text/javascript" src="../../resources/js/plugins/metisMenu/jquery.metisMenu.js" 
        data-th-src="@{/resources/js/plugins/metisMenu/jquery.metisMenu.js}"></script>
        
    <!-- SB Admin Scripts - Include with every page -->
    <script type="text/javascript" src="../../resources/js/sb-admin.js" 
        data-th-src="@{/resources/js/sb-admin.js}"></script>

</body>
</html>
Similar to the edit page, the highlighted section above loops through all of the roles associated with this permission and displays only this subset of roles on this delete page.


4. Screenshots of the Permission CRUD pages are shown below.
permission-list.html

permission-edit.html

permission-delete.html


We now have working CRUD functionality for our Permission Entity.  In the next development post we will look at session timeout and login redirection.

Code at GitHub: https://github.com/dtr-trading/spring-ex17-crud-perm

Wednesday, May 14, 2014

Spring Thymeleaf Permissions CRUD - Part 1

This post will build on the last development series that began with Spring Thymeleaf Roles CRUD - Part 1.  In this post we will create the CRUD controller and pages for Permissions.  The Permissions maintenance pages will be a bit more complicated than the CRUD pages for the other entities.

1. To begin, we will update the security configuration file so that we can access the permissions pages.  The additional entry is highlighted in the code below.
package com.dtr.oas.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import com.dtr.oas.exception.AccessDeniedExceptionHandler;

@Configuration
@EnableWebMvcSecurity
@ComponentScan(basePackageClasses=com.dtr.oas.service.UserServiceImpl.class)
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    public void configureGlobal(UserDetailsService userDetailsService, AuthenticationManagerBuilder auth) throws Exception {
        auth
            .userDetailsService(userDetailsService);
    }
   
    @Autowired
    AccessDeniedExceptionHandler accessDeniedExceptionHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/resources/**").permitAll()
                .antMatchers("/error/**").permitAll()
                .antMatchers("/strategy/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasRole("ADMIN")
                .antMatchers("/role/**").hasRole("ADMIN")
                .antMatchers("/permission/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/")
                .permitAll()
                .and()
            .logout()
                .permitAll()
                .and()
            .exceptionHandling()
                .accessDeniedHandler(accessDeniedExceptionHandler);
    }
}

2. As with the User CRUD pages, we will need a DTO to transfer state to the controller. This is required because Permission objects holds references to one or more Role objects. Since Java objects cannot be returned from the browser, we will use a Permission DTO that will hold references to Role Id numbers, which can be returned from a browser. The DTO is shown below.
package com.dtr.oas.controller;
import java.io.Serializable;
import java.util.List;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.NotEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;

public class PermissionDTO implements Serializable {

    private static final long serialVersionUID = -2707641174043923410L;
    static Logger logger = LoggerFactory.getLogger(PermissionDTO.class);
    
    private int id;

    @NotNull(message = "{error.permission.permissionname.null}")
    @NotEmpty(message = "{error.permission.permissionname.empty}")
    @Size(max = 50, message = "{permission.permissionname.role.max}")
    private String permissionname;
    
    private List<Integer> permRoles;
    
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getPermissionname() {
        return permissionname;
    }

    public void setPermissionname(String permissionname) {
        this.permissionname = permissionname;
    }

    public List<Integer> getPermRoles() {
        return permRoles;
    }

    public void setPermRoles(List<Integer> permRoles) {
        this.permRoles = permRoles;
    }

    @Override
    public String toString() {
        return String.format("%s(id=%d, permissionname='%s')", 
                this.getClass().getSimpleName(), 
                this.getId(), this.getPermissionname());
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null)
            return false;

        if (o instanceof PermissionDTO) {
            final PermissionDTO other = (PermissionDTO) o;
            return Objects.equal(getId(), other.getId())
                    && Objects.equal(getPermissionname(), other.getPermissionname());
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(getId(), getPermissionname());
    }

}

3. The last Java file that we will create is the controller for the Permission object. The controller is shown below, with some of the more important details described after the code.
package com.dtr.oas.controller;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.dtr.oas.exception.DuplicatePermissionException;
import com.dtr.oas.exception.PermissionNotFoundException;
import com.dtr.oas.exception.RoleNotFoundException;
import com.dtr.oas.model.Permission;
import com.dtr.oas.model.Role;
import com.dtr.oas.service.PermissionService;
import com.dtr.oas.service.RoleService;

@Controller
@RequestMapping(value = "/permission")
@PreAuthorize("denyAll")
public class PermissionController {

    static Logger logger = LoggerFactory.getLogger(PermissionController.class);
    static String businessObject = "permission"; //used in RedirectAttributes messages 
    
    @Autowired
    private RoleService roleService;

    @Autowired
    private PermissionService permissionService;

    @Autowired
    private MessageSource messageSource;

    @ModelAttribute("allRoles")
    @PreAuthorize("hasAnyRole('CTRL_PERM_LIST_GET','CTRL_PERM_EDIT_GET')")
    public List<Role> getAllRoles() {
        return roleService.getRoles();
    }

    @RequestMapping(value = {"/", "/list"}, method = RequestMethod.GET)
    @PreAuthorize("hasRole('CTRL_PERM_LIST_GET')")
    public String listPermissions(Model model) {
        logger.debug("IN: Permission/list-GET");

        List<Permission> permissions = permissionService.getPermissions();
        model.addAttribute("permissions", permissions);

        // if there was an error in /add, we do not want to overwrite
        // the existing user object containing the errors.
        if (!model.containsAttribute("permissionDTO")) {
            logger.debug("Adding PermissionDTO object to model");
            PermissionDTO permissionDTO = new PermissionDTO();
            model.addAttribute("permissionDTO", permissionDTO);
        }
        return "permission-list";
    }
    
    @RequestMapping(value = "/add", method = RequestMethod.POST)
    @PreAuthorize("hasRole('CTRL_PERM_ADD_POST')")
    public String addPermission(@Valid @ModelAttribute PermissionDTO permissionDTO,
            BindingResult result, RedirectAttributes redirectAttrs) {
        
        logger.debug("IN: Permission/add-POST");
        logger.debug("  DTO: " + permissionDTO.toString());

        if (result.hasErrors()) {
            logger.debug("PermissionDTO add error: " + result.toString());
            redirectAttrs.addFlashAttribute("org.springframework.validation.BindingResult.permissionDTO", result);
            redirectAttrs.addFlashAttribute("permissionDTO", permissionDTO);
            return "redirect:/permission/list";
        } else {
            Permission perm = new Permission();

            try {
                perm = getPermission(permissionDTO);
                permissionService.addPermission(perm);
                String message = messageSource.getMessage("ctrl.message.success.add", 
                        new Object[] {businessObject, perm.getPermissionname()}, Locale.US);
                redirectAttrs.addFlashAttribute("message", message);
                return "redirect:/permission/list";
            } catch (DuplicatePermissionException e) {
                String message = messageSource.getMessage("ctrl.message.error.duplicate", 
                        new Object[] {businessObject, permissionDTO.getPermissionname()}, Locale.US);
                redirectAttrs.addFlashAttribute("error", message);
                return "redirect:/permission/list";
           } catch (RoleNotFoundException e) {
               String message = messageSource.getMessage("ctrl.message.error.notfound", 
                       new Object[] {"role ids", permissionDTO.getPermRoles().toString()}, Locale.US);
               redirectAttrs.addFlashAttribute("error", message);
                return "redirect:/permission/list";
            }
        }
    }

    @RequestMapping(value = "/edit", method = RequestMethod.POST)
    @PreAuthorize("hasRole('CTRL_PERM_EDIT_POST')")
    public String editPermission(@Valid @ModelAttribute PermissionDTO permissionDTO,
            BindingResult result, RedirectAttributes redirectAttrs,
            @RequestParam(value = "action", required = true) String action) {

        logger.debug("IN: Permission/edit-POST: " + action);

        if (action.equals(messageSource.getMessage("button.action.cancel", null, Locale.US))) {
            String message = messageSource.getMessage("ctrl.message.success.cancel", 
                    new Object[] {"Edit", businessObject, permissionDTO.getPermissionname()}, Locale.US);
            redirectAttrs.addFlashAttribute("message", message);
        } else if (result.hasErrors()) {
            logger.debug("Permission-edit error: " + result.toString());
            redirectAttrs.addFlashAttribute("org.springframework.validation.BindingResult.permissionDTO", result);
            redirectAttrs.addFlashAttribute("permissionDTO", permissionDTO);
            return "redirect:/permission/edit?id=" + permissionDTO.getId();
        } else if (action.equals(messageSource.getMessage("button.action.save",  null, Locale.US))) {
            logger.debug("Permission/edit-POST:  " + permissionDTO.toString());
            try {
                Permission permission = getPermission(permissionDTO);
                permissionService.updatePermission(permission);
                String message = messageSource.getMessage("ctrl.message.success.update", 
                        new Object[] {businessObject, permissionDTO.getPermissionname()}, Locale.US);
                redirectAttrs.addFlashAttribute("message", message);
            } catch (DuplicatePermissionException unf) {
                String message = messageSource.getMessage("ctrl.message.error.duplicate", 
                        new Object[] {businessObject, permissionDTO.getPermissionname()}, Locale.US);
                redirectAttrs.addFlashAttribute("error", message);
                return "redirect:/permission/list";
            } catch (PermissionNotFoundException unf) {
                String message = messageSource.getMessage("ctrl.message.error.notfound", 
                        new Object[] {businessObject, permissionDTO.getPermissionname()}, Locale.US);
                redirectAttrs.addFlashAttribute("error", message);
                return "redirect:/permission/list";
            } catch (RoleNotFoundException unf) {
                String message = messageSource.getMessage("ctrl.message.error.notfound", 
                        new Object[] {"role ids", permissionDTO.getPermRoles().toString()}, Locale.US);
                redirectAttrs.addFlashAttribute("error", message);
                return "redirect:/permission/list";
            }
        }
        return "redirect:/permission/list";
    }
    @RequestMapping(value = "/edit", method = RequestMethod.GET)
    @PreAuthorize("hasRole('CTRL_PERM_EDIT_GET')")
    public String editPermissionPage(@RequestParam(value = "id", required = true)
            Integer id, Model model, RedirectAttributes redirectAttrs) {

        logger.debug("IN: Permission/edit-GET:  ID to query = " + id);

        try {
            if (!model.containsAttribute("permissionDTO")) {
                logger.debug("Adding permissionDTO object to model");
                Permission perm = permissionService.getPermission(id);
                PermissionDTO permissionDTO = getPermissionDTO(perm);
                logger.debug("Permission/edit-GET:  " + permissionDTO.toString());
                model.addAttribute("permissionDTO", permissionDTO);
            }
            return "permission-edit";
        } catch (PermissionNotFoundException e) {
            String message = messageSource.getMessage("ctrl.message.error.notfound", 
                    new Object[] {"user id", id}, Locale.US);
            model.addAttribute("error", message);
            return "redirect:/permission/list";
        }
    }

    
    
    @RequestMapping(value = "/delete", method = RequestMethod.GET)
    @PreAuthorize("hasRole('CTRL_PERM_DELETE_GET')")
    public String deletePermission(
            @RequestParam(value = "id", required = true) Integer id,
            @RequestParam(value = "phase", required = true) String phase,
            Model model, RedirectAttributes redirectAttrs) {

        Permission permission;
        try {
            permission = permissionService.getPermission(id);
        } catch (PermissionNotFoundException e) {
            String message = messageSource.getMessage("ctrl.message.error.notfound", 
                    new Object[] {"permission id", id}, Locale.US);
            redirectAttrs.addFlashAttribute("error", message);
            return "redirect:/permission/list";
        }

        logger.debug("IN: Permission/delete-GET | id = " + id + " | phase = " + phase + " | " + permission.toString());

        if (phase.equals(messageSource.getMessage("button.action.cancel", null, Locale.US))) {
            String message = messageSource.getMessage("ctrl.message.success.cancel", 
                    new Object[] {"Delete", businessObject, permission.getPermissionname()}, Locale.US);
            redirectAttrs.addFlashAttribute("message", message);
            return "redirect:/permission/list";
        } else if (phase.equals(messageSource.getMessage("button.action.stage", null, Locale.US))) {
            logger.debug("     deleting permission : " + permission.toString());
            model.addAttribute("permission", permission);
            return "permission-delete";
        } else if (phase.equals(messageSource.getMessage("button.action.delete", null, Locale.US))) {
            try {
                permissionService.deletePermission(permission.getId());
                String message = messageSource.getMessage("ctrl.message.success.delete", 
                        new Object[] {businessObject, permission.getPermissionname()}, Locale.US);
                redirectAttrs.addFlashAttribute("message", message);
                return "redirect:/permission/list";
            } catch (PermissionNotFoundException e) {
                String message = messageSource.getMessage("ctrl.message.error.notfound", 
                        new Object[] {"permission id", id}, Locale.US);
               redirectAttrs.addFlashAttribute("error", message);
                return "redirect:/permission/list";
           }
        }

        return "redirect:/permission/list";
    }

    @PreAuthorize("hasAnyRole('CTRL_PERM_EDIT_GET','CTRL_PERM_DELETE_GET')")
    public PermissionDTO getPermissionDTO(Permission perm) {
        List<Integer> roleIdList = new ArrayList<Integer>();
        PermissionDTO permDTO = new PermissionDTO();
        permDTO.setId(perm.getId());
        permDTO.setPermissionname(perm.getPermissionname());
        for (Role role : perm.getPermRoles()) {
            roleIdList.add(role.getId());
        }
        permDTO.setPermRoles(roleIdList);
        return permDTO;
    }

    @PreAuthorize("hasAnyRole('CTRL_PERM_ADD_POST','CTRL_PERM_EDIT_POST')")
    public Permission getPermission(PermissionDTO permissionDTO) throws RoleNotFoundException {
        Set<Role> roleList = new HashSet<Role>();
        Permission perm = new Permission();
        Role role = new Role();
        
        perm.setId(permissionDTO.getId());
        perm.setPermissionname(permissionDTO.getPermissionname());
        if (permissionDTO.getPermRoles() != null) {
            for (Integer roleId : permissionDTO.getPermRoles()) {
                role = roleService.getRole(roleId);
                logger.debug("  ROLE: " + role.toString());
                roleList.add(role);
            }
            perm.setPermRoles(roleList);
        }
        logger.debug("  PERM: " + perm.toString());
        return perm;
    }
}
Similar to the User controller, we are autowiring two services: one for the PermissionService and one for the RoleService. Permission maintenance will provide the capability to add/update/delete Roles associated with a permission.

The list, add, edit, and delete methods look nearly identical to the same methods on the User controller, and were discussed in the User CRUD posts. Note that the Permission add and edit pages have to handle an exception thrown if a role is not found...not very likely, but it needs to be handled.

At the bottom of the conroller code are methods to convert DTOs to Entities, and Entities to DTOs. The functionality to notice is the handling of the Permission objects associations with Role objects.

In part 2, we will create the three Thymeleaf pages and show some screenshots of the Permissions CRUD pages.