Lines 13-20
Link Here
|
13 |
* See the License for the specific language governing permissions and |
13 |
* See the License for the specific language governing permissions and |
14 |
* limitations under the License. |
14 |
* limitations under the License. |
15 |
*/ |
15 |
*/ |
16 |
|
16 |
|
17 |
|
|
|
18 |
package org.apache.catalina.realm; |
17 |
package org.apache.catalina.realm; |
19 |
|
18 |
|
20 |
|
19 |
|
Lines 22-28
Link Here
|
22 |
import java.security.acl.Group; |
21 |
import java.security.acl.Group; |
23 |
import java.util.ArrayList; |
22 |
import java.util.ArrayList; |
24 |
import java.util.Enumeration; |
23 |
import java.util.Enumeration; |
|
|
24 |
import java.util.HashMap; |
25 |
import java.util.Iterator; |
25 |
import java.util.Iterator; |
|
|
26 |
import java.util.List; |
27 |
import java.util.Map; |
26 |
|
28 |
|
27 |
import javax.security.auth.Subject; |
29 |
import javax.security.auth.Subject; |
28 |
import javax.security.auth.login.AccountExpiredException; |
30 |
import javax.security.auth.login.AccountExpiredException; |
Lines 62-68
Link Here
|
62 |
* this Realm:</p> |
64 |
* this Realm:</p> |
63 |
* <ul> |
65 |
* <ul> |
64 |
* <li>The JAAS <code>LoginModule</code> is assumed to return a |
66 |
* <li>The JAAS <code>LoginModule</code> is assumed to return a |
65 |
* <code>Subject with at least one <code>Principal</code> instance |
67 |
* <code>Subject</code> with at least one <code>Principal</code> instance |
66 |
* representing the user himself or herself, and zero or more separate |
68 |
* representing the user himself or herself, and zero or more separate |
67 |
* <code>Principals</code> representing the security roles authorized |
69 |
* <code>Principals</code> representing the security roles authorized |
68 |
* for this user.</li> |
70 |
* for this user.</li> |
Lines 87-95
Link Here
|
87 |
* <li>It is a configuration error for the JAAS login method to return a |
89 |
* <li>It is a configuration error for the JAAS login method to return a |
88 |
* validated <code>Subject</code> without a <code>Principal</code> that |
90 |
* validated <code>Subject</code> without a <code>Principal</code> that |
89 |
* matches the "user classes" list.</li> |
91 |
* matches the "user classes" list.</li> |
|
|
92 |
* <li>By default, the enclosing Container's name serves as the |
93 |
* application name used to obtain the JAAS LoginContext ("Catalina" in |
94 |
* a default installation). Tomcat must be able to find an application |
95 |
* with this name in the JAAS configuration file. Here is a hypothetical |
96 |
* configuration file entry for a database-oriented login module that uses |
97 |
* a Tomcat-managed JNDI database resource: |
98 |
* <blockquote><pre>Catalina { |
99 |
org.foobar.auth.DatabaseLoginModule REQUIRED |
100 |
JNDI_RESOURCE=jdbc/AuthDB |
101 |
USER_TABLE=users |
102 |
USER_ID_COLUMN=id |
103 |
USER_NAME_COLUMN=name |
104 |
USER_CREDENTIAL_COLUMN=password |
105 |
ROLE_TABLE=roles |
106 |
ROLE_NAME_COLUMN=name |
107 |
PRINCIPAL_FACTORY=org.foobar.auth.impl.SimplePrincipalFactory; |
108 |
};</pre></blockquote></li> |
109 |
* <li>To set the JAAS configuration file |
110 |
* location, set the <code>CATALINA_OPTS</code> environment variable |
111 |
* similar to the following: |
112 |
<blockquote><code>CATALINA_OPTS="-Djava.security.auth.login.config=$CATALINA_HOME/conf/jaas.config"</code></blockquote> |
113 |
* </li> |
114 |
* <li>As part of the login process, JAASRealm registers its own <code>CallbackHandler</code>, |
115 |
* called (unsurprisingly) <code>JAASCallbackHandler</code>. This handler supplies the |
116 |
* HTTP requests's username and credentials to the user-supplied <code>LoginModule</code></li> |
117 |
* <li>As with other <code>Realm</code> implementations, digested passwords are supported if |
118 |
* the <code><Realm></code> element in <code>server.xml</code> contains a |
119 |
* <code>digest</code> attribute; <code>JAASCallbackHandler</code> will digest the password |
120 |
* prior to passing it back to the <code>LoginModule</code></li> |
90 |
* </ul> |
121 |
* </ul> |
91 |
* |
122 |
* |
92 |
* @author Craig R. McClanahan |
123 |
* @author Craig R. McClanahan |
|
|
124 |
* @author Andrew R. Jaquith |
93 |
* @version $Revision: 1.6 $ $Date: 2004/02/27 14:58:45 $ |
125 |
* @version $Revision: 1.6 $ $Date: 2004/02/27 14:58:45 $ |
94 |
*/ |
126 |
*/ |
95 |
|
127 |
|
Lines 103-122
Link Here
|
103 |
|
135 |
|
104 |
/** |
136 |
/** |
105 |
* The application name passed to the JAAS <code>LoginContext</code>, |
137 |
* The application name passed to the JAAS <code>LoginContext</code>, |
106 |
* which uses it to select the set of relevant <code>LoginModules</code>. |
138 |
* which uses it to select the set of relevant <code>LoginModule</code>s. |
107 |
*/ |
139 |
*/ |
108 |
protected String appName = null; |
140 |
protected String appName = null; |
109 |
|
141 |
|
110 |
|
142 |
|
111 |
/** |
143 |
/** |
112 |
* Descriptive information about this Realm implementation. |
144 |
* Descriptive information about this <code>Realm</code> implementation. |
113 |
*/ |
145 |
*/ |
114 |
protected static final String info = |
146 |
protected static final String info = |
115 |
"org.apache.catalina.realm.JAASRealm/1.0"; |
147 |
"org.apache.catalina.realm.JAASRealm/1.0"; |
116 |
|
148 |
|
117 |
|
149 |
|
118 |
/** |
150 |
/** |
119 |
* Descriptive information about this Realm implementation. |
151 |
* Descriptive information about this <code>Realm</code> implementation. |
120 |
*/ |
152 |
*/ |
121 |
protected static final String name = "JAASRealm"; |
153 |
protected static final String name = "JAASRealm"; |
122 |
|
154 |
|
Lines 124-130
Link Here
|
124 |
/** |
156 |
/** |
125 |
* The list of role class names, split out for easy processing. |
157 |
* The list of role class names, split out for easy processing. |
126 |
*/ |
158 |
*/ |
127 |
protected ArrayList roleClasses = new ArrayList(); |
159 |
protected List roleClasses = new ArrayList(); |
128 |
|
160 |
|
129 |
|
161 |
|
130 |
/** |
162 |
/** |
Lines 137-158
Link Here
|
137 |
/** |
169 |
/** |
138 |
* The set of user class names, split out for easy processing. |
170 |
* The set of user class names, split out for easy processing. |
139 |
*/ |
171 |
*/ |
140 |
protected ArrayList userClasses = new ArrayList(); |
172 |
protected List userClasses = new ArrayList(); |
|
|
173 |
|
174 |
/** |
175 |
* Map associating each user <code>Principal</code> object |
176 |
* with an array of role <code>Principal</code>s. |
177 |
* This Map is read when <code>hasRole</code> is called. |
178 |
*/ |
179 |
protected Map roleMap = new HashMap(); |
141 |
|
180 |
|
142 |
|
181 |
|
143 |
// ------------------------------------------------------------- Properties |
182 |
// ------------------------------------------------------------- Properties |
144 |
|
183 |
|
145 |
|
184 |
|
146 |
/** |
185 |
/** |
147 |
* setter for the appName member variable |
186 |
* setter for the <code>appName</code> member variable |
148 |
* @deprecated JAAS should use the Engine ( domain ) name and webpp/host overrides |
187 |
* @deprecated JAAS should use the <code>Engine</code> (domain) name and webpp/host overrides |
149 |
*/ |
188 |
*/ |
150 |
public void setAppName(String name) { |
189 |
public void setAppName(String name) { |
151 |
appName = name; |
190 |
appName = name; |
152 |
} |
191 |
} |
153 |
|
192 |
|
154 |
/** |
193 |
/** |
155 |
* getter for the appName member variable |
194 |
* getter for the <code>appName</code> member variable |
156 |
*/ |
195 |
*/ |
157 |
public String getAppName() { |
196 |
public String getAppName() { |
158 |
return appName; |
197 |
return appName; |
Lines 168-174
Link Here
|
168 |
} |
207 |
} |
169 |
|
208 |
|
170 |
/** |
209 |
/** |
171 |
* Comma-delimited list of <code>javax.security.Principal</code> classes |
210 |
* Comma-delimited list of <code>java.security.Principal</code> classes |
172 |
* that represent security roles. |
211 |
* that represent security roles. |
173 |
*/ |
212 |
*/ |
174 |
protected String roleClassNames = null; |
213 |
protected String roleClassNames = null; |
Lines 177-182
Link Here
|
177 |
return (this.roleClassNames); |
216 |
return (this.roleClassNames); |
178 |
} |
217 |
} |
179 |
|
218 |
|
|
|
219 |
/** |
220 |
* Sets the list of comma-delimited classes that represent |
221 |
* roles. The classes in the list must implement <code>java.security.Principal</code>. |
222 |
* When this accessor is called (for example, by a <code>Digester</code> |
223 |
* instance parsing the |
224 |
* configuration file), it will parse the class names and store the resulting |
225 |
* string(s) into the <code>ArrayList</code> field </code>roleClasses</code>. |
226 |
*/ |
180 |
public void setRoleClassNames(String roleClassNames) { |
227 |
public void setRoleClassNames(String roleClassNames) { |
181 |
this.roleClassNames = roleClassNames; |
228 |
this.roleClassNames = roleClassNames; |
182 |
roleClasses.clear(); |
229 |
roleClasses.clear(); |
Lines 200-206
Link Here
|
200 |
|
247 |
|
201 |
|
248 |
|
202 |
/** |
249 |
/** |
203 |
* Comma-delimited list of <code>javax.security.Principal</code> classes |
250 |
* Comma-delimited list of <code>java.security.Principal</code> classes |
204 |
* that represent individual users. |
251 |
* that represent individual users. |
205 |
*/ |
252 |
*/ |
206 |
protected String userClassNames = null; |
253 |
protected String userClassNames = null; |
Lines 209-214
Link Here
|
209 |
return (this.userClassNames); |
256 |
return (this.userClassNames); |
210 |
} |
257 |
} |
211 |
|
258 |
|
|
|
259 |
/** |
260 |
* Sets the list of comma-delimited classes that represent individual |
261 |
* users. The classes in the list must implement <code>java.security.Principal</code>. |
262 |
* When this accessor is called (for example, by a <code>Digester</code> |
263 |
* instance parsing the |
264 |
* configuration file), it will parse the class names and store the resulting |
265 |
* string(s) into the <code>ArrayList</code> field </code>userClasses</code>. |
266 |
*/ |
212 |
public void setUserClassNames(String userClassNames) { |
267 |
public void setUserClassNames(String userClassNames) { |
213 |
this.userClassNames = userClassNames; |
268 |
this.userClassNames = userClassNames; |
214 |
userClasses.clear(); |
269 |
userClasses.clear(); |
Lines 235-241
Link Here
|
235 |
|
290 |
|
236 |
|
291 |
|
237 |
/** |
292 |
/** |
238 |
* Return the Principal associated with the specified username and |
293 |
* Return the <code>Principal</code> associated with the specified username and |
239 |
* credentials, if there is one; otherwise return <code>null</code>. |
294 |
* credentials, if there is one; otherwise return <code>null</code>. |
240 |
* |
295 |
* |
241 |
* If there are any errors with the JDBC connection, executing |
296 |
* If there are any errors with the JDBC connection, executing |
Lines 243-249
Link Here
|
243 |
* event is also logged, and the connection will be closed so that |
298 |
* event is also logged, and the connection will be closed so that |
244 |
* a subsequent request will automatically re-open it. |
299 |
* a subsequent request will automatically re-open it. |
245 |
* |
300 |
* |
246 |
* @param username Username of the Principal to look up |
301 |
* @param username Username of the <code>Principal</code> to look up |
247 |
* @param credentials Password or other credentials to use in |
302 |
* @param credentials Password or other credentials to use in |
248 |
* authenticating this username |
303 |
* authenticating this username |
249 |
*/ |
304 |
*/ |
Lines 255-261
Link Here
|
255 |
if( appName==null ) appName="Tomcat"; |
310 |
if( appName==null ) appName="Tomcat"; |
256 |
|
311 |
|
257 |
if( log.isDebugEnabled()) |
312 |
if( log.isDebugEnabled()) |
258 |
log.debug("Authenticating " + appName + " " + username); |
313 |
log.debug(sm.getString("jaasRealm.beginLogin", username, appName)); |
259 |
|
314 |
|
260 |
// What if the LoginModule is in the container class loader ? |
315 |
// What if the LoginModule is in the container class loader ? |
261 |
// |
316 |
// |
Lines 273-279
Link Here
|
273 |
} |
328 |
} |
274 |
|
329 |
|
275 |
if( log.isDebugEnabled()) |
330 |
if( log.isDebugEnabled()) |
276 |
log.debug("Login context created " + username); |
331 |
log.debug(sm.getString("jaasRealm.loginContextCreated", username)); |
277 |
|
332 |
|
278 |
// Negotiate a login via this LoginContext |
333 |
// Negotiate a login via this LoginContext |
279 |
Subject subject = null; |
334 |
Subject subject = null; |
Lines 306-321
Link Here
|
306 |
} |
361 |
} |
307 |
|
362 |
|
308 |
if( log.isDebugEnabled()) |
363 |
if( log.isDebugEnabled()) |
309 |
log.debug("Getting principal " + subject); |
364 |
log.debug(sm.getString("jaasRealm.loginContextCreated", username)); |
310 |
|
365 |
|
311 |
// Return the appropriate Principal for this authenticated Subject |
366 |
// Return the appropriate Principal for this authenticated Subject |
312 |
Principal principal = createPrincipal(username, subject); |
367 |
Principal principal = createPrincipal(username, subject); |
313 |
if (principal == null) { |
368 |
if (principal == null) { |
314 |
log.debug(sm.getString("jaasRealm.authenticateFailure", username)); |
369 |
log.error(sm.getString("jaasRealm.authenticateFailure", username)); |
315 |
return (null); |
370 |
return (null); |
316 |
} |
371 |
} |
317 |
if (log.isDebugEnabled()) { |
372 |
if (log.isDebugEnabled()) { |
318 |
log.debug(sm.getString("jaasRealm.authenticateSuccess", username)); |
373 |
log.info(sm.getString("jaasRealm.authenticateSuccess", username, principal.getName())); |
319 |
} |
374 |
} |
320 |
|
375 |
|
321 |
return (principal); |
376 |
return (principal); |
Lines 325-330
Link Here
|
325 |
} |
380 |
} |
326 |
} |
381 |
} |
327 |
|
382 |
|
|
|
383 |
/** |
384 |
* Returns <code>true</code> if the specified user <code>Principal</code> has the specified |
385 |
* security role, within the context of this <code>Realm</code>; otherwise return |
386 |
* <code>false</code>. This will be true when |
387 |
* an associated role <code>Principal</code> can be found whose <code>getName</code> |
388 |
* method returns a <code>String</code> equalling the specified role. |
389 |
* @param principal <code>Principal</code> for whom the role is to be checked |
390 |
* @param role Security role to be checked |
391 |
*/ |
392 |
public boolean hasRole(Principal principal, String role) { |
393 |
|
394 |
// Based on code from JAASRealm |
395 |
if (log.isDebugEnabled()) { |
396 |
log.debug(sm.getString("jaasRealm.isInRole.start", principal.getName(), role)); |
397 |
} |
398 |
if ((principal == null) || (role == null) || |
399 |
(roleMap.get(principal) == null)) { |
400 |
if (log.isDebugEnabled()) { |
401 |
log.debug(sm.getString("jaasRealm.isInRole.noPrincipalOrRole")); |
402 |
} |
403 |
return (false); |
404 |
} |
405 |
List roles = (List)roleMap.get(principal); |
406 |
if (log.isDebugEnabled()) { |
407 |
log.debug(sm.getString("jaasRealm.isInRole.principalCached", String.valueOf(roles.size()))); |
408 |
} |
409 |
for (Iterator it = roles.iterator(); it.hasNext();) { |
410 |
Principal possessedRole = (Principal)it.next(); |
411 |
String possessedRoleName = possessedRole.getName(); |
412 |
if (log.isDebugEnabled()) { |
413 |
log.debug(sm.getString("jaasRealm.isInRole.possessesRole", possessedRole.getName())); |
414 |
} |
415 |
if (possessedRoleName.equals(role)) { |
416 |
if (log.isDebugEnabled()) { |
417 |
log.debug(sm.getString("jaasRealm.isInRole.match")); |
418 |
} |
419 |
return (true); |
420 |
} |
421 |
} |
422 |
if (log.isDebugEnabled()) { |
423 |
log.error(sm.getString("jaasRealm.isInRole.noMatch")); |
424 |
} |
425 |
return (false); |
426 |
} |
427 |
|
328 |
|
428 |
|
329 |
// -------------------------------------------------------- Package Methods |
429 |
// -------------------------------------------------------- Package Methods |
330 |
|
430 |
|
Lines 333-339
Link Here
|
333 |
|
433 |
|
334 |
|
434 |
|
335 |
/** |
435 |
/** |
336 |
* Return a short name for this Realm implementation. |
436 |
* Return a short name for this <code>Realm</code> implementation. |
337 |
*/ |
437 |
*/ |
338 |
protected String getName() { |
438 |
protected String getName() { |
339 |
|
439 |
|
Lines 353-359
Link Here
|
353 |
|
453 |
|
354 |
|
454 |
|
355 |
/** |
455 |
/** |
356 |
* Return the Principal associated with the given user name. |
456 |
* Return the <code>Principal</code> associated with the given user name. |
357 |
*/ |
457 |
*/ |
358 |
protected Principal getPrincipal(String username) { |
458 |
protected Principal getPrincipal(String username) { |
359 |
|
459 |
|
Lines 363-431
Link Here
|
363 |
|
463 |
|
364 |
|
464 |
|
365 |
/** |
465 |
/** |
366 |
* Construct and return a <code>java.security.Principal</code> instance |
466 |
* Identify and return a <code>java.security.Principal</code> instance |
367 |
* representing the authenticated user for the specified Subject. If no |
467 |
* representing the authenticated user for the specified <code>Subject</code>. |
368 |
* such Principal can be constructed, return <code>null</code>. |
468 |
* If no such Principal can be constructed, return <code>null</code>. |
369 |
* |
469 |
* @param subject The <code>Subject</code> representing the logged-in user |
370 |
* @param subject The Subject representing the logged in user |
|
|
371 |
*/ |
470 |
*/ |
372 |
protected Principal createPrincipal(String username, Subject subject) { |
471 |
protected Principal createPrincipal(String username, Subject subject) { |
373 |
// Prepare to scan the Principals for this Subject |
472 |
// Prepare to scan the Principals for this Subject |
374 |
String password = null; // Will not be carried forward |
473 |
String password = null; // Will not be carried forward |
375 |
ArrayList roles = new ArrayList(); |
474 |
List roles = new ArrayList(); |
376 |
|
475 |
Principal userPrincipal = null; |
|
|
476 |
|
377 |
// Scan the Principals for this Subject |
477 |
// Scan the Principals for this Subject |
378 |
Iterator principals = subject.getPrincipals().iterator(); |
478 |
Iterator principals = subject.getPrincipals().iterator(); |
379 |
while (principals.hasNext()) { |
479 |
while (principals.hasNext()) { |
380 |
Principal principal = (Principal) principals.next(); |
480 |
Principal principal = (Principal) principals.next(); |
381 |
// No need to look further - that's our own stuff |
|
|
382 |
if( principal instanceof GenericPrincipal ) { |
383 |
if( log.isDebugEnabled() ) |
384 |
log.debug("Found old GenericPrincipal " + principal ); |
385 |
return principal; |
386 |
} |
387 |
String principalClass = principal.getClass().getName(); |
481 |
String principalClass = principal.getClass().getName(); |
388 |
if( log.isDebugEnabled() ) |
482 |
if( log.isDebugEnabled() ) { |
389 |
log.info("Principal: " + principalClass + " " + principal); |
483 |
log.debug(sm.getString("jaasRealm.checkPrincipal", principal, principalClass)); |
390 |
|
|
|
391 |
if (userClasses.contains(principalClass)) { |
392 |
// Override the default - which is the original user, accepted by |
393 |
// the friendly LoginManager |
394 |
username = principal.getName(); |
395 |
} |
484 |
} |
396 |
if (roleClasses.contains(principalClass)) { |
485 |
if (userPrincipal == null && userClasses.contains(principalClass)) { |
397 |
roles.add(principal.getName()); |
486 |
userPrincipal = principal; |
|
|
487 |
if( log.isDebugEnabled() ) { |
488 |
log.debug(sm.getString("jaasRealm.userPrincipalSuccess", principal.getName())); |
489 |
} |
398 |
} |
490 |
} |
399 |
// Same as Jboss - that's a pretty clean solution |
491 |
if (roleClasses.contains(principalClass)) { |
400 |
if( (principal instanceof Group) && |
492 |
roles.add(principal); |
401 |
"Roles".equals( principal.getName())) { |
493 |
if( log.isDebugEnabled() ) { |
402 |
Group grp=(Group)principal; |
494 |
log.debug(sm.getString("jaasRealm.rolePrincipalAdd", principal.getName())); |
403 |
Enumeration en=grp.members(); |
|
|
404 |
while( en.hasMoreElements() ) { |
405 |
Principal roleP=(Principal)en.nextElement(); |
406 |
roles.add( roleP.getName()); |
407 |
} |
495 |
} |
408 |
|
|
|
409 |
} |
496 |
} |
410 |
} |
497 |
} |
411 |
|
498 |
|
412 |
// Create the resulting Principal for our authenticated user |
499 |
// Print failure message if needed |
413 |
if (username != null) { |
500 |
if (userPrincipal == null) { |
414 |
return (new GenericPrincipal(this, username, password, roles)); |
501 |
if (log.isDebugEnabled()) { |
415 |
} else { |
502 |
log.debug(sm.getString("jaasRealm.userPrincipalFailure")); |
416 |
return (null); |
503 |
log.debug(sm.getString("jaasRealm.rolePrincipalFailure")); |
|
|
504 |
} |
505 |
} |
506 |
else { |
507 |
if (roles.size() == 0) { |
508 |
if (log.isDebugEnabled()) { |
509 |
log.debug(sm.getString("jaasRealm.rolePrincipalFailure")); |
510 |
} |
511 |
} |
512 |
else { |
513 |
roleMap.put(userPrincipal, roles); |
514 |
if (log.isDebugEnabled()) { |
515 |
log.debug(sm.getString("jaasRealm.rolePrincipalSuccess", String.valueOf(roles.size()))); |
516 |
log.debug(sm.getString("jaasRealm.cachePrincipal", userPrincipal.getName(), String.valueOf(roles.size()))); |
517 |
} |
518 |
} |
417 |
} |
519 |
} |
|
|
520 |
|
521 |
// Return the resulting Principal for our authenticated user |
522 |
return userPrincipal; |
418 |
} |
523 |
} |
419 |
|
524 |
|
420 |
|
525 |
|
421 |
// ------------------------------------------------------ Lifecycle Methods |
526 |
// ------------------------------------------------------ Lifecycle Methods |
422 |
|
527 |
|
423 |
|
528 |
|
424 |
/** |
529 |
/** |
425 |
* |
530 |
* |
426 |
* Prepare for active use of the public methods of this Component. |
531 |
* Prepare for active use of the public methods of this <code>Component</code>. |
427 |
* |
532 |
* |
428 |
* @exception LifecycleException if this component detects a fatal error |
533 |
* @exception <code>LifecycleException</code> if this component detects a fatal error |
429 |
* that prevents it from being started |
534 |
* that prevents it from being started |
430 |
*/ |
535 |
*/ |
431 |
public void start() throws LifecycleException { |
536 |
public void start() throws LifecycleException { |
Lines 437-445
Link Here
|
437 |
|
542 |
|
438 |
|
543 |
|
439 |
/** |
544 |
/** |
440 |
* Gracefully shut down active use of the public methods of this Component. |
545 |
* Gracefully shut down active use of the public methods of this <code>Component</code>. |
441 |
* |
546 |
* |
442 |
* @exception LifecycleException if this component detects a fatal error |
547 |
* @exception <code>LifecycleException</code> if this component detects a fatal error |
443 |
* that needs to be reported |
548 |
* that needs to be reported |
444 |
*/ |
549 |
*/ |
445 |
public void stop() throws LifecycleException { |
550 |
public void stop() throws LifecycleException { |