View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.shiro.samples.aspectj.bank;
20  
21  import org.apache.shiro.SecurityUtils;
22  import org.apache.shiro.authz.annotation.RequiresPermissions;
23  import org.apache.shiro.samples.aspectj.bank.AccountTransaction.TransactionType;
24  import org.apache.shiro.subject.Subject;
25  import org.slf4j.Logger;
26  import org.slf4j.LoggerFactory;
27  
28  import java.util.ArrayList;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Map;
32  
33  public class SecureBankService implements BankService {
34  
35      private static final Logger log = LoggerFactory.getLogger(SecureBankService.class);
36      private volatile boolean _isRunning;
37      private final List<Account> _accounts;
38      private Map<Long, Account> _accountsById;
39  
40      /**
41       * Creates a new {@link SecureBankService} instance.
42       */
43      public SecureBankService() {
44          _accounts = new ArrayList<Account>();
45          _accountsById = new HashMap<Long, Account>();
46      }
47  
48      /**
49       * Starts this service
50       */
51      public void start() throws Exception {
52          _isRunning = true;
53          log.info("Bank service started");
54      }
55  
56      /**
57       * Stop this service
58       */
59      public void dispose() {
60          log.info("Stopping bank service...");
61          _isRunning = false;
62  
63          synchronized (_accounts) {
64              _accountsById.clear();
65              _accounts.clear();
66          }
67  
68          log.info("Bank service stopped");
69      }
70  
71      /**
72       * Internal utility method that validate the internal state of this service.
73       */
74      protected void assertServiceState() {
75          if (!_isRunning) {
76              throw new IllegalStateException("This bank service is not running");
77          }
78      }
79  
80      public int getAccountCount() {
81          return _accounts.size();
82      }
83  
84      /* (non-Javadoc)
85      * @see com.connectif.trilogy.root.security.BankService#createNewAccount(java.lang.String)
86      */
87  
88      @RequiresPermissions("bankAccount:create")
89      public long createNewAccount(String anOwnerName) {
90          assertServiceState();
91          log.info("Creating new account for " + anOwnerName);
92  
93          synchronized (_accounts) {
94              Account account = new Account(anOwnerName);
95              account.setCreatedBy(getCurrentUsername());
96              _accounts.add(account);
97              _accountsById.put(account.getId(), account);
98  
99              log.debug("Created new account: " + account);
100             return account.getId();
101         }
102     }
103 
104     /* (non-Javadoc)
105     * @see com.connectif.trilogy.root.security.BankService#searchAccountIdsByOwner(java.lang.String)
106     */
107 
108     public long[] searchAccountIdsByOwner(String anOwnerName) {
109         assertServiceState();
110         log.info("Searching existing accounts for " + anOwnerName);
111 
112         ArrayList<Account> matchAccounts = new ArrayList<Account>();
113         synchronized (_accounts) {
114             for (Account a : _accounts) {
115                 if (a.getOwnerName().toLowerCase().contains(anOwnerName.toLowerCase())) {
116                     matchAccounts.add(a);
117                 }
118             }
119         }
120 
121         long[] accountIds = new long[matchAccounts.size()];
122         int index = 0;
123         for (Account a : matchAccounts) {
124             accountIds[index++] = a.getId();
125         }
126 
127         log.debug("Found " + accountIds.length + " account(s) matching the name " + anOwnerName);
128         return accountIds;
129     }
130 
131     /* (non-Javadoc)
132     * @see com.connectif.trilogy.root.security.BankService#getOwnerOf(long)
133     */
134 
135     @RequiresPermissions("bankAccount:read")
136     public String getOwnerOf(long anAccountId) throws AccountNotFoundException {
137         assertServiceState();
138         log.info("Getting owner of account " + anAccountId);
139 
140         Account a = safellyRetrieveAccountForId(anAccountId);
141         return a.getOwnerName();
142     }
143 
144     /* (non-Javadoc)
145     * @see com.connectif.trilogy.root.security.BankService#getBalanceOf(long)
146     */
147 
148     @RequiresPermissions("bankAccount:read")
149     public double getBalanceOf(long anAccountId) throws AccountNotFoundException {
150         assertServiceState();
151         log.info("Getting balance of account " + anAccountId);
152 
153         Account a = safellyRetrieveAccountForId(anAccountId);
154         return a.getBalance();
155     }
156 
157     /* (non-Javadoc)
158     * @see com.connectif.trilogy.root.security.BankService#depositInto(long, double)
159     */
160 
161     @RequiresPermissions("bankAccount:operate")
162     public double depositInto(long anAccountId, double anAmount) throws AccountNotFoundException, InactiveAccountException {
163         assertServiceState();
164         log.info("Making deposit of " + anAmount + " into account " + anAccountId);
165 
166         try {
167             Account a = safellyRetrieveAccountForId(anAccountId);
168             AccountTransaction tx = AccountTransaction.createDepositTx(anAccountId, anAmount);
169             tx.setCreatedBy(getCurrentUsername());
170             log.debug("Created a new transaction " + tx);
171 
172             a.applyTransaction(tx);
173             log.debug("New balance of account " + a.getId() + " after deposit is " + a.getBalance());
174 
175             return a.getBalance();
176 
177         } catch (NotEnoughFundsException nefe) {
178             throw new IllegalStateException("Should never happen", nefe);
179         }
180     }
181 
182     /* (non-Javadoc)
183     * @see com.connectif.trilogy.root.security.BankService#withdrawFrom(long, double)
184     */
185 
186     @RequiresPermissions("bankAccount:operate")
187     public double withdrawFrom(long anAccountId, double anAmount) throws AccountNotFoundException, NotEnoughFundsException, InactiveAccountException {
188         assertServiceState();
189         log.info("Making withdrawal of " + anAmount + " from account " + anAccountId);
190 
191         Account a = safellyRetrieveAccountForId(anAccountId);
192         AccountTransaction tx = AccountTransaction.createWithdrawalTx(anAccountId, anAmount);
193         tx.setCreatedBy(getCurrentUsername());
194         log.debug("Created a new transaction " + tx);
195 
196         a.applyTransaction(tx);
197         log.debug("New balance of account " + a.getId() + " after withdrawal is " + a.getBalance());
198 
199         return a.getBalance();
200     }
201 
202     /* (non-Javadoc)
203     * @see com.connectif.trilogy.root.security.BankService#getTxHistoryFor(long)
204     */
205 
206     @RequiresPermissions("bankAccount:read")
207     public TxLog[] getTxHistoryFor(long anAccountId) throws AccountNotFoundException {
208         assertServiceState();
209         log.info("Getting transactions of account " + anAccountId);
210 
211         Account a = safellyRetrieveAccountForId(anAccountId);
212 
213         TxLog[] txs = new TxLog[a.getTransactions().size()];
214         int index = 0;
215         for (AccountTransaction tx : a.getTransactions()) {
216             log.debug("Retrieved transaction " + tx);
217 
218             if (TransactionType.DEPOSIT == tx.getType()) {
219                 txs[index++] = new TxLog(tx.getCreationDate(), tx.getAmount(), tx.getCreatedBy());
220             } else {
221                 txs[index++] = new TxLog(tx.getCreationDate(), -1.0d * tx.getAmount(), tx.getCreatedBy());
222             }
223         }
224 
225         return txs;
226     }
227 
228     /* (non-Javadoc)
229     * @see com.connectif.trilogy.root.security.BankService#closeAccount(long)
230     */
231 
232     @RequiresPermissions("bankAccount:close")
233     public double closeAccount(long anAccountId) throws AccountNotFoundException, InactiveAccountException {
234         assertServiceState();
235         log.info("Closing account " + anAccountId);
236 
237         Account a = safellyRetrieveAccountForId(anAccountId);
238         if (!a.isActive()) {
239             throw new InactiveAccountException("The account " + anAccountId + " is already closed");
240         }
241 
242         try {
243             AccountTransaction tx = AccountTransaction.createWithdrawalTx(a.getId(), a.getBalance());
244             tx.setCreatedBy(getCurrentUsername());
245             log.debug("Created a new transaction " + tx);
246             a.applyTransaction(tx);
247             a.setActive(false);
248 
249             log.debug("Account " + a.getId() + " is now closed and an amount of " + tx.getAmount() + " is given to the owner");
250             return tx.getAmount();
251 
252         } catch (NotEnoughFundsException nefe) {
253             throw new IllegalStateException("Should never happen", nefe);
254         }
255     }
256 
257     /* (non-Javadoc)
258     * @see com.connectif.trilogy.root.security.BankService#isAccountActive(long)
259     */
260 
261     @RequiresPermissions("bankAccount:read")
262     public boolean isAccountActive(long anAccountId) throws AccountNotFoundException {
263         assertServiceState();
264         log.info("Getting active status of account " + anAccountId);
265 
266         Account a = safellyRetrieveAccountForId(anAccountId);
267         return a.isActive();
268     }
269 
270 
271     /**
272      * Internal method that safelly (concurrency-wise) retrieves an account from the id passed in.
273      *
274      * @param anAccountId The identifier of the account to retrieve.
275      * @return The account instance retrieved.
276      * @throws AccountNotFoundException If no account is found for the provided identifier.
277      */
278     protected Account safellyRetrieveAccountForId(long anAccountId) throws AccountNotFoundException {
279         Account account = null;
280         synchronized (_accounts) {
281             account = _accountsById.get(anAccountId);
282         }
283 
284         if (account == null) {
285             throw new AccountNotFoundException("No account found for the id " + anAccountId);
286         }
287 
288         log.info("Retrieved account " + account);
289         return account;
290     }
291 
292     /**
293      * Internal utility method to retrieve the username of the current authenticated user.
294      *
295      * @return The name.
296      */
297     protected String getCurrentUsername() {
298         Subject subject = SecurityUtils.getSubject();
299         if (subject == null || subject.getPrincipal() == null || !subject.isAuthenticated()) {
300             throw new IllegalStateException("Unable to retrieve the current authenticated subject");
301         }
302         return SecurityUtils.getSubject().getPrincipal().toString();
303     }
304 }