2 // ========================================================================
3 // Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
19 package org.eclipse.jetty.security;
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.List;
30 import java.util.Map.Entry;
32 import java.util.concurrent.CopyOnWriteArrayList;
33 import java.util.concurrent.CopyOnWriteArraySet;
35 import javax.servlet.HttpConstraintElement;
36 import javax.servlet.HttpMethodConstraintElement;
37 import javax.servlet.ServletSecurityElement;
38 import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
39 import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
41 import org.eclipse.jetty.http.HttpStatus;
42 import org.eclipse.jetty.http.PathMap;
43 import org.eclipse.jetty.server.HttpChannel;
44 import org.eclipse.jetty.server.HttpConfiguration;
45 import org.eclipse.jetty.server.Request;
46 import org.eclipse.jetty.server.Response;
47 import org.eclipse.jetty.server.UserIdentity;
48 import org.eclipse.jetty.server.handler.ContextHandler;
49 import org.eclipse.jetty.util.log.Log;
50 import org.eclipse.jetty.util.log.Logger;
51 import org.eclipse.jetty.util.security.Constraint;
53 /* ------------------------------------------------------------ */
55 * ConstraintSecurityHandler
57 * Handler to enforce SecurityConstraints. This implementation is servlet spec
58 * 3.1 compliant and pre-computes the constraint combinations for runtime
62 public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware
64 private static final Logger LOG = Log.getLogger(SecurityHandler.class); //use same as SecurityHandler
66 private static final String OMISSION_SUFFIX = ".omission";
67 private static final String ALL_METHODS = "*";
68 private final List<ConstraintMapping> _constraintMappings= new CopyOnWriteArrayList<>();
69 private final Set<String> _roles = new CopyOnWriteArraySet<>();
70 private final PathMap<Map<String, RoleInfo>> _constraintMap = new PathMap<>();
71 private boolean _denyUncoveredMethods = false;
74 /* ------------------------------------------------------------ */
75 public static Constraint createConstraint()
77 return new Constraint();
80 /* ------------------------------------------------------------ */
84 public static Constraint createConstraint(Constraint constraint)
88 return (Constraint)constraint.clone();
90 catch (CloneNotSupportedException e)
92 throw new IllegalStateException (e);
96 /* ------------------------------------------------------------ */
98 * Create a security constraint
101 * @param authenticate
103 * @param dataConstraint
105 public static Constraint createConstraint (String name, boolean authenticate, String[] roles, int dataConstraint)
107 Constraint constraint = createConstraint();
109 constraint.setName(name);
110 constraint.setAuthenticate(authenticate);
111 constraint.setRoles(roles);
112 constraint.setDataConstraint(dataConstraint);
117 /* ------------------------------------------------------------ */
122 public static Constraint createConstraint (String name, HttpConstraintElement element)
124 return createConstraint(name, element.getRolesAllowed(), element.getEmptyRoleSemantic(), element.getTransportGuarantee());
128 /* ------------------------------------------------------------ */
131 * @param rolesAllowed
132 * @param permitOrDeny
135 public static Constraint createConstraint (String name, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport)
137 Constraint constraint = createConstraint();
139 if (rolesAllowed == null || rolesAllowed.length==0)
141 if (permitOrDeny.equals(EmptyRoleSemantic.DENY))
143 //Equivalent to <auth-constraint> with no roles
144 constraint.setName(name+"-Deny");
145 constraint.setAuthenticate(true);
149 //Equivalent to no <auth-constraint>
150 constraint.setName(name+"-Permit");
151 constraint.setAuthenticate(false);
156 //Equivalent to <auth-constraint> with list of <security-role-name>s
157 constraint.setAuthenticate(true);
158 constraint.setRoles(rolesAllowed);
159 constraint.setName(name+"-RolesAllowed");
162 //Equivalent to //<user-data-constraint><transport-guarantee>CONFIDENTIAL</transport-guarantee></user-data-constraint>
163 constraint.setDataConstraint((transport.equals(TransportGuarantee.CONFIDENTIAL)?Constraint.DC_CONFIDENTIAL:Constraint.DC_NONE));
169 /* ------------------------------------------------------------ */
172 * @param constraintMappings
174 public static List<ConstraintMapping> getConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
176 if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
177 return Collections.emptyList();
179 List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
180 for (ConstraintMapping mapping:constraintMappings)
182 if (pathSpec.equals(mapping.getPathSpec()))
184 mappings.add(mapping);
191 /* ------------------------------------------------------------ */
192 /** Take out of the constraint mappings those that match the
196 * @param constraintMappings a new list minus the matching constraints
198 public static List<ConstraintMapping> removeConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
200 if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
201 return Collections.emptyList();
203 List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
204 for (ConstraintMapping mapping:constraintMappings)
206 //Remove the matching mappings by only copying in non-matching mappings
207 if (!pathSpec.equals(mapping.getPathSpec()))
209 mappings.add(mapping);
217 /* ------------------------------------------------------------ */
218 /** Generate Constraints and ContraintMappings for the given url pattern and ServletSecurityElement
222 * @param securityElement
225 public static List<ConstraintMapping> createConstraintsWithMappingsForPath (String name, String pathSpec, ServletSecurityElement securityElement)
227 List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
229 //Create a constraint that will describe the default case (ie if not overridden by specific HttpMethodConstraints)
230 Constraint httpConstraint = null;
231 ConstraintMapping httpConstraintMapping = null;
233 if (securityElement.getEmptyRoleSemantic() != EmptyRoleSemantic.PERMIT ||
234 securityElement.getRolesAllowed().length != 0 ||
235 securityElement.getTransportGuarantee() != TransportGuarantee.NONE)
237 httpConstraint = ConstraintSecurityHandler.createConstraint(name, securityElement);
239 //Create a mapping for the pathSpec for the default case
240 httpConstraintMapping = new ConstraintMapping();
241 httpConstraintMapping.setPathSpec(pathSpec);
242 httpConstraintMapping.setConstraint(httpConstraint);
243 mappings.add(httpConstraintMapping);
247 //See Spec 13.4.1.2 p127
248 List<String> methodOmissions = new ArrayList<String>();
250 //make constraint mappings for this url for each of the HttpMethodConstraintElements
251 Collection<HttpMethodConstraintElement> methodConstraintElements = securityElement.getHttpMethodConstraints();
252 if (methodConstraintElements != null)
254 for (HttpMethodConstraintElement methodConstraintElement:methodConstraintElements)
256 //Make a Constraint that captures the <auth-constraint> and <user-data-constraint> elements supplied for the HttpMethodConstraintElement
257 Constraint methodConstraint = ConstraintSecurityHandler.createConstraint(name, methodConstraintElement);
258 ConstraintMapping mapping = new ConstraintMapping();
259 mapping.setConstraint(methodConstraint);
260 mapping.setPathSpec(pathSpec);
261 if (methodConstraintElement.getMethodName() != null)
263 mapping.setMethod(methodConstraintElement.getMethodName());
264 //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
265 methodOmissions.add(methodConstraintElement.getMethodName());
267 mappings.add(mapping);
270 //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
271 //UNLESS the default constraint contains all default values. In that case, we won't add it. See Servlet Spec 3.1 pg 129
272 if (methodOmissions.size() > 0 && httpConstraintMapping != null)
273 httpConstraintMapping.setMethodOmissions(methodOmissions.toArray(new String[methodOmissions.size()]));
281 /* ------------------------------------------------------------ */
283 * @return Returns the constraintMappings.
286 public List<ConstraintMapping> getConstraintMappings()
288 return _constraintMappings;
291 /* ------------------------------------------------------------ */
293 public Set<String> getRoles()
298 /* ------------------------------------------------------------ */
300 * Process the constraints following the combining rules in Servlet 3.0 EA
301 * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
303 * @param constraintMappings
304 * The constraintMappings to set, from which the set of known roles
307 public void setConstraintMappings(List<ConstraintMapping> constraintMappings)
309 setConstraintMappings(constraintMappings,null);
313 * Process the constraints following the combining rules in Servlet 3.0 EA
314 * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
316 * @param constraintMappings
317 * The constraintMappings to set as array, from which the set of known roles
318 * is determined. Needed to retain API compatibility for 7.x
320 public void setConstraintMappings( ConstraintMapping[] constraintMappings )
322 setConstraintMappings( Arrays.asList(constraintMappings), null);
325 /* ------------------------------------------------------------ */
327 * Process the constraints following the combining rules in Servlet 3.0 EA
328 * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
330 * @param constraintMappings
331 * The constraintMappings to set.
332 * @param roles The known roles (or null to determine them from the mappings)
335 public void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles)
337 _constraintMappings.clear();
338 _constraintMappings.addAll(constraintMappings);
342 roles = new HashSet<>();
343 for (ConstraintMapping cm : constraintMappings)
345 String[] cmr = cm.getConstraint().getRoles();
349 if (!ALL_METHODS.equals(r))
358 for (ConstraintMapping mapping : _constraintMappings)
360 processConstraintMapping(mapping);
365 /* ------------------------------------------------------------ */
367 * Set the known roles.
368 * This may be overridden by a subsequent call to {@link #setConstraintMappings(ConstraintMapping[])} or
369 * {@link #setConstraintMappings(List, Set)}.
370 * @param roles The known roles (or null to determine them from the mappings)
372 public void setRoles(Set<String> roles)
375 _roles.addAll(roles);
380 /* ------------------------------------------------------------ */
382 * @see org.eclipse.jetty.security.ConstraintAware#addConstraintMapping(org.eclipse.jetty.security.ConstraintMapping)
385 public void addConstraintMapping(ConstraintMapping mapping)
387 _constraintMappings.add(mapping);
388 if (mapping.getConstraint()!=null && mapping.getConstraint().getRoles()!=null)
390 //allow for lazy role naming: if a role is named in a security constraint, try and
391 //add it to the list of declared roles (ie as if it was declared with a security-role
392 for (String role : mapping.getConstraint().getRoles())
394 if ("*".equals(role) || "**".equals(role))
402 processConstraintMapping(mapping);
406 /* ------------------------------------------------------------ */
408 * @see org.eclipse.jetty.security.ConstraintAware#addRole(java.lang.String)
411 public void addRole(String role)
413 //add to list of declared roles
414 boolean modified = _roles.add(role);
415 if (isStarted() && modified)
417 // Add the new role to currently defined any role role infos
418 for (Map<String,RoleInfo> map : _constraintMap.values())
420 for (RoleInfo info : map.values())
422 if (info.isAnyRole())
429 /* ------------------------------------------------------------ */
431 * @see org.eclipse.jetty.security.SecurityHandler#doStart()
434 protected void doStart() throws Exception
436 _constraintMap.clear();
437 if (_constraintMappings!=null)
439 for (ConstraintMapping mapping : _constraintMappings)
441 processConstraintMapping(mapping);
445 //Servlet Spec 3.1 pg 147 sec 13.8.4.2 log paths for which there are uncovered http methods
446 checkPathsWithUncoveredHttpMethods();
452 /* ------------------------------------------------------------ */
454 protected void doStop() throws Exception
457 _constraintMap.clear();
461 /* ------------------------------------------------------------ */
463 * Create and combine the constraint with the existing processed
468 protected void processConstraintMapping(ConstraintMapping mapping)
470 Map<String, RoleInfo> mappings = _constraintMap.get(mapping.getPathSpec());
471 if (mappings == null)
473 mappings = new HashMap<String,RoleInfo>();
474 _constraintMap.put(mapping.getPathSpec(),mappings);
476 RoleInfo allMethodsRoleInfo = mappings.get(ALL_METHODS);
477 if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden())
480 if (mapping.getMethodOmissions() != null && mapping.getMethodOmissions().length > 0)
482 processConstraintMappingWithMethodOmissions(mapping, mappings);
486 String httpMethod = mapping.getMethod();
487 if (httpMethod==null)
488 httpMethod=ALL_METHODS;
489 RoleInfo roleInfo = mappings.get(httpMethod);
490 if (roleInfo == null)
492 roleInfo = new RoleInfo();
493 mappings.put(httpMethod,roleInfo);
494 if (allMethodsRoleInfo != null)
496 roleInfo.combine(allMethodsRoleInfo);
499 if (roleInfo.isForbidden())
502 //add in info from the constraint
503 configureRoleInfo(roleInfo, mapping);
505 if (roleInfo.isForbidden())
507 if (httpMethod.equals(ALL_METHODS))
510 mappings.put(ALL_METHODS,roleInfo);
515 //combine with any entry that covers all methods
516 if (httpMethod == null)
518 for (Map.Entry<String, RoleInfo> entry : mappings.entrySet())
520 if (entry.getKey() != null)
522 RoleInfo specific = entry.getValue();
523 specific.combine(roleInfo);
530 /* ------------------------------------------------------------ */
531 /** Constraints that name method omissions are dealt with differently.
532 * We create an entry in the mappings with key "<method>.omission". This entry
533 * is only ever combined with other omissions for the same method to produce a
534 * consolidated RoleInfo. Then, when we wish to find the relevant constraints for
535 * a given Request (in prepareConstraintInfo()), we consult 3 types of entries in
536 * the mappings: an entry that names the method of the Request specifically, an
537 * entry that names constraints that apply to all methods, entries of the form
538 * <method>.omission, where the method of the Request is not named in the omission.
542 protected void processConstraintMappingWithMethodOmissions (ConstraintMapping mapping, Map<String, RoleInfo> mappings)
544 String[] omissions = mapping.getMethodOmissions();
545 StringBuilder sb = new StringBuilder();
546 for (int i=0; i<omissions.length; i++)
550 sb.append(omissions[i]);
552 sb.append(OMISSION_SUFFIX);
553 RoleInfo ri = new RoleInfo();
554 mappings.put(sb.toString(), ri);
555 configureRoleInfo(ri, mapping);
559 /* ------------------------------------------------------------ */
561 * Initialize or update the RoleInfo from the constraint
565 protected void configureRoleInfo (RoleInfo ri, ConstraintMapping mapping)
567 Constraint constraint = mapping.getConstraint();
568 boolean forbidden = constraint.isForbidden();
569 ri.setForbidden(forbidden);
571 //set up the data constraint (NOTE: must be done after setForbidden, as it nulls out the data constraint
572 //which we need in order to do combining of omissions in prepareConstraintInfo
573 UserDataConstraint userDataConstraint = UserDataConstraint.get(mapping.getConstraint().getDataConstraint());
574 ri.setUserDataConstraint(userDataConstraint);
576 //if forbidden, no point setting up roles
577 if (!ri.isForbidden())
580 boolean checked = mapping.getConstraint().getAuthenticate();
581 ri.setChecked(checked);
585 if (mapping.getConstraint().isAnyRole())
587 // * means matches any defined role
588 for (String role : _roles)
592 else if (mapping.getConstraint().isAnyAuth())
594 //being authenticated is sufficient, not necessary to check roles
599 //user must be in one of the named roles
600 String[] newRoles = mapping.getConstraint().getRoles();
601 for (String role : newRoles)
603 //check role has been defined
604 if (!_roles.contains(role))
605 throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles);
614 /* ------------------------------------------------------------ */
616 * Find constraints that apply to the given path.
617 * In order to do this, we consult 3 different types of information stored in the mappings for each path - each mapping
618 * represents a merged set of user data constraints, roles etc -:
620 * <li>A mapping of an exact method name </li>
621 * <li>A mapping with key * that matches every method name</li>
622 * <li>Mappings with keys of the form "<method>.<method>.<method>.omission" that indicates it will match every method name EXCEPT those given</li>
625 * @see org.eclipse.jetty.security.SecurityHandler#prepareConstraintInfo(java.lang.String, org.eclipse.jetty.server.Request)
628 protected RoleInfo prepareConstraintInfo(String pathInContext, Request request)
630 Map<String, RoleInfo> mappings = (Map<String, RoleInfo>)_constraintMap.match(pathInContext);
632 if (mappings != null)
634 String httpMethod = request.getMethod();
635 RoleInfo roleInfo = mappings.get(httpMethod);
636 if (roleInfo == null)
638 //No specific http-method names matched
639 List<RoleInfo> applicableConstraints = new ArrayList<RoleInfo>();
641 //Get info for constraint that matches all methods if it exists
642 RoleInfo all = mappings.get(ALL_METHODS);
644 applicableConstraints.add(all);
647 //Get info for constraints that name method omissions where target method name is not omitted
648 //(ie matches because target method is not omitted, hence considered covered by the constraint)
649 for (Entry<String, RoleInfo> entry: mappings.entrySet())
651 if (entry.getKey() != null && entry.getKey().endsWith(OMISSION_SUFFIX) && ! entry.getKey().contains(httpMethod))
652 applicableConstraints.add(entry.getValue());
655 if (applicableConstraints.size() == 0 && isDenyUncoveredHttpMethods())
657 roleInfo = new RoleInfo();
658 roleInfo.setForbidden(true);
660 else if (applicableConstraints.size() == 1)
661 roleInfo = applicableConstraints.get(0);
664 roleInfo = new RoleInfo();
665 roleInfo.setUserDataConstraint(UserDataConstraint.None);
667 for (RoleInfo r:applicableConstraints)
680 protected boolean checkUserDataPermissions(String pathInContext, Request request, Response response, RoleInfo roleInfo) throws IOException
682 if (roleInfo == null)
685 if (roleInfo.isForbidden())
688 UserDataConstraint dataConstraint = roleInfo.getUserDataConstraint();
689 if (dataConstraint == null || dataConstraint == UserDataConstraint.None)
692 HttpConfiguration httpConfig = HttpChannel.getCurrentHttpChannel().getHttpConfiguration();
694 if (dataConstraint == UserDataConstraint.Confidential || dataConstraint == UserDataConstraint.Integral)
696 if (request.isSecure())
699 if (httpConfig.getSecurePort() > 0)
701 String scheme = httpConfig.getSecureScheme();
702 int port = httpConfig.getSecurePort();
703 String url = ("https".equalsIgnoreCase(scheme) && port==443)
704 ? "https://"+request.getServerName()+request.getRequestURI()
705 : scheme + "://" + request.getServerName() + ":" + port + request.getRequestURI();
706 if (request.getQueryString() != null)
707 url += "?" + request.getQueryString();
708 response.setContentLength(0);
709 response.sendRedirect(url);
712 response.sendError(HttpStatus.FORBIDDEN_403,"!Secure");
714 request.setHandled(true);
719 throw new IllegalArgumentException("Invalid dataConstraint value: " + dataConstraint);
725 protected boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo)
727 return constraintInfo != null && ((RoleInfo)constraintInfo).isChecked();
731 /* ------------------------------------------------------------ */
733 * @see org.eclipse.jetty.security.SecurityHandler#checkWebResourcePermissions(java.lang.String, org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response, java.lang.Object, org.eclipse.jetty.server.UserIdentity)
736 protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity)
739 if (constraintInfo == null)
743 RoleInfo roleInfo = (RoleInfo)constraintInfo;
745 if (!roleInfo.isChecked())
750 //handle ** role constraint
751 if (roleInfo.isAnyAuth() && request.getUserPrincipal() != null)
756 //check if user is any of the allowed roles
757 boolean isUserInRole = false;
758 for (String role : roleInfo.getRoles())
760 if (userIdentity.isUserInRole(role, null))
767 //handle * role constraint
768 if (roleInfo.isAnyRole() && request.getUserPrincipal() != null && isUserInRole)
782 /* ------------------------------------------------------------ */
784 public void dump(Appendable out,String indent) throws IOException
786 // TODO these should all be beans
787 dumpBeans(out,indent,
788 Collections.singleton(getLoginService()),
789 Collections.singleton(getIdentityService()),
790 Collections.singleton(getAuthenticator()),
791 Collections.singleton(_roles),
792 _constraintMap.entrySet());
795 /* ------------------------------------------------------------ */
797 * @see org.eclipse.jetty.security.ConstraintAware#setDenyUncoveredHttpMethods(boolean)
800 public void setDenyUncoveredHttpMethods(boolean deny)
802 _denyUncoveredMethods = deny;
805 /* ------------------------------------------------------------ */
807 public boolean isDenyUncoveredHttpMethods()
809 return _denyUncoveredMethods;
813 /* ------------------------------------------------------------ */
815 * Servlet spec 3.1 pg. 147.
818 public boolean checkPathsWithUncoveredHttpMethods()
820 Set<String> paths = getPathsWithUncoveredHttpMethods();
821 if (paths != null && !paths.isEmpty())
824 LOG.warn("{} has uncovered http methods for path: {}",ContextHandler.getCurrentContext(), p);
825 if (LOG.isDebugEnabled())
826 LOG.debug(new Throwable());
833 /* ------------------------------------------------------------ */
835 * Servlet spec 3.1 pg. 147.
836 * The container must check all the combined security constraint
837 * information and log any methods that are not protected and the
838 * urls at which they are not protected
840 * @return list of paths for which there are uncovered methods
842 public Set<String> getPathsWithUncoveredHttpMethods ()
844 //if automatically denying uncovered methods, there are no uncovered methods
845 if (_denyUncoveredMethods)
846 return Collections.emptySet();
848 Set<String> uncoveredPaths = new HashSet<String>();
850 for (String path:_constraintMap.keySet())
852 Map<String, RoleInfo> methodMappings = _constraintMap.get(path);
853 //Each key is either:
854 // : an exact method name
855 // : * which means that the constraint applies to every method
856 // : a name of the form <method>.<method>.<method>.omission, which means it applies to every method EXCEPT those named
857 if (methodMappings.get(ALL_METHODS) != null)
858 continue; //can't be any uncovered methods for this url path
860 boolean hasOmissions = omissionsExist(path, methodMappings);
862 for (String method:methodMappings.keySet())
864 if (method.endsWith(OMISSION_SUFFIX))
866 Set<String> omittedMethods = getOmittedMethods(method);
867 for (String m:omittedMethods)
869 if (!methodMappings.containsKey(m))
870 uncoveredPaths.add(path);
875 //an exact method name
877 //a http-method does not have http-method-omission to cover the other method names
878 uncoveredPaths.add(path);
883 return uncoveredPaths;
886 /* ------------------------------------------------------------ */
888 * Check if any http method omissions exist in the list of method
889 * to auth info mappings.
892 * @param methodMappings
895 protected boolean omissionsExist (String path, Map<String, RoleInfo> methodMappings)
897 if (methodMappings == null)
899 boolean hasOmissions = false;
900 for (String m:methodMappings.keySet())
902 if (m.endsWith(OMISSION_SUFFIX))
909 /* ------------------------------------------------------------ */
911 * Given a string of the form <method>.<method>.omission
912 * split out the individual method names.
917 protected Set<String> getOmittedMethods (String omission)
919 if (omission == null || !omission.endsWith(OMISSION_SUFFIX))
920 return Collections.emptySet();
922 String[] strings = omission.split("\\.");
923 Set<String> methods = new HashSet<String>();
924 for (int i=0;i<strings.length-1;i++)
925 methods.add(strings[i]);