001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2014-2016 ForgeRock AS. 015 */ 016 017package org.forgerock.openam.entitlement; 018 019import com.sun.identity.entitlement.AndCondition; 020import com.sun.identity.entitlement.AndSubject; 021import com.sun.identity.entitlement.DenyOverride; 022import com.sun.identity.entitlement.EntitlementCombiner; 023import com.sun.identity.entitlement.EntitlementCondition; 024import com.sun.identity.entitlement.EntitlementSubject; 025import com.sun.identity.entitlement.NoSubject; 026import com.sun.identity.entitlement.NotCondition; 027import com.sun.identity.entitlement.NotSubject; 028import com.sun.identity.entitlement.OrCondition; 029import com.sun.identity.entitlement.OrSubject; 030import com.sun.identity.entitlement.ResourceAttribute; 031import com.sun.identity.entitlement.StaticAttributes; 032import com.sun.identity.entitlement.UserAttributes; 033 034import java.util.Map; 035import java.util.ServiceLoader; 036import java.util.Set; 037import java.util.concurrent.ConcurrentHashMap; 038import java.util.concurrent.ConcurrentMap; 039 040/** 041 * Provides methods for discovering and loading entitlements conditions and subject implementations. Builds upon the 042 * standard Java {@link ServiceLoader} mechanism to allow additional entitlement condition and subject implementations 043 * to be registered by client extensions (see {@link EntitlementModule}). 044 * 045 * @since 12.0.0 046 * @supported.all.api 047 */ 048public final class EntitlementRegistry { 049 050 private final ConcurrentMap<String, Class<? extends EntitlementCondition>> conditions = 051 new ConcurrentHashMap<String, Class<? extends EntitlementCondition>>(); 052 private final ConcurrentMap<String, Class<? extends EntitlementSubject>> subjects = 053 new ConcurrentHashMap<String, Class<? extends EntitlementSubject>>(); 054 private final ConcurrentMap<String, Class<? extends ResourceAttribute>> attributes = 055 new ConcurrentHashMap<String, Class<? extends ResourceAttribute>>(); 056 private final ConcurrentMap<String, Class<? extends EntitlementCombiner>> combiners = 057 new ConcurrentHashMap<String, Class<? extends EntitlementCombiner>>(); 058 059 /** 060 * Lazy initialisation holder for loading and caching module instances. We use lazy loading to reduce startup 061 * overhead and also to ensure that classpaths are fully initialised before loading services. 062 */ 063 private enum ServiceLoaderHolder { 064 INSTANCE; 065 final ServiceLoader<EntitlementModule> loader = ServiceLoader.load(EntitlementModule.class); 066 } 067 068 /** 069 * Loads all available {@link EntitlementModule} instances and registers them with a new entitlement registry. 070 * Each invocation of this method will attempt to load any known entitlement modules as per 071 * {@link ServiceLoader#load(Class)}. Previously loaded modules will be cached but any newly available modules 072 * will be loaded. 073 * 074 * @return an entitlement registry populated with all known entitlement modules. 075 */ 076 public static EntitlementRegistry load() { 077 078 EntitlementRegistry registry = new EntitlementRegistry(); 079 080 // Register standard logical condition and subject types. 081 registry.registerConditionType("AND", AndCondition.class); 082 registry.registerConditionType("OR", OrCondition.class); 083 registry.registerConditionType("NOT", NotCondition.class); 084 085 registry.registerSubjectType("AND", AndSubject.class); 086 registry.registerSubjectType("OR", OrSubject.class); 087 registry.registerSubjectType("NOT", NotSubject.class); 088 089 /* These conditions are not tested and were removed for OpenAM 12 release. 090 They might be reintroduced in a future release. 091 092 // Standard OpenAM entitlement conditions (policy conditions will be loaded later) 093 registry.registerConditionType(NumericAttributeCondition.class); 094 registry.registerConditionType(AttributeLookupCondition.class); 095 registry.registerConditionType(StringAttributeCondition.class); 096 // Standard OpenAM subjects 097 registry.registerSubjectType(AttributeSubject.class); 098 */ 099 100 // Standard OpenAM subjects 101 registry.registerSubjectType("NONE", NoSubject.class); 102 103 // Standard OpenAM resource attribute types 104 registry.registerAttributeType("User", UserAttributes.class); 105 registry.registerAttributeType("Static", StaticAttributes.class); 106 107 // Standard OpenAM Decision Combiners 108 registry.registerDecisionCombiner(DenyOverride.class); 109 110 ServiceLoader<ConditionTypeRegistry> conditionTypeRegistries = ServiceLoader.load(ConditionTypeRegistry.class); 111 for (ConditionTypeRegistry conditionTypeRegistry : conditionTypeRegistries) { 112 for (Class<? extends EntitlementCondition> condition : conditionTypeRegistry.getEnvironmentConditions()) { 113 registry.registerConditionType(condition); 114 } 115 116 for (Class<? extends EntitlementSubject> condition : conditionTypeRegistry.getSubjectConditions()) { 117 registry.registerSubjectType(condition); 118 } 119 } 120 121 for (EntitlementModule provider : ServiceLoaderHolder.INSTANCE.loader) { 122 provider.registerCustomTypes(registry); 123 } 124 125 return registry; 126 } 127 128 /** 129 * Registers an entitlement condition type with the given short name (used in RESTful API calls and in the UI). 130 * Note: short names must be unique across all condition types. 131 * 132 * @param name the short name of the condition type. 133 * @param type the condition type to register. 134 * @throws NameAlreadyRegisteredException if the short name is already registered. 135 */ 136 public void registerConditionType(String name, Class<? extends EntitlementCondition> type) { 137 register(name, conditions, type); 138 } 139 140 /** 141 * Registers an entitlement condition type using a short name generated from the type name. The short name is 142 * generated as the simple name of the class minus any {@code Condition} suffix. For example, a condition 143 * type {@code org.forgerock.openam.entitlement.TestCondition} would be registered with the short name {@code Test}. 144 * 145 * @param type the condition type to register. 146 * @throws NameAlreadyRegisteredException if the short name is already registered. 147 */ 148 public void registerConditionType(Class<? extends EntitlementCondition> type) { 149 String name = type.getSimpleName().replace("Condition", ""); 150 registerConditionType(name, type); 151 } 152 153 /** 154 * Returns the condition type associated with the given short name, or null if no such condition is registered. 155 * 156 * @param name the short name of the condition type to get. 157 * @return the associated condition type or null if no matching condition type is registered. 158 */ 159 public Class<? extends EntitlementCondition> getConditionType(String name) { 160 return conditions.get(name); 161 } 162 163 /** 164 * Registers an entitlement combiner. 165 * 166 * @param type the condition type to register. 167 * @throws NameAlreadyRegisteredException if the short name is already registered. 168 */ 169 public void registerDecisionCombiner(Class<? extends EntitlementCombiner> type) { 170 register(type.getSimpleName(), combiners, type); 171 } 172 173 /** 174 * Registers an entitlement combiner with a given name. 175 * 176 * @param type the combiner type to register. 177 * @throws NameAlreadyRegisteredException if the short name is already registered. 178 */ 179 public void registerDecisionCombiner(String name, Class<? extends EntitlementCombiner> type) { 180 register(name, combiners, type); 181 } 182 183 /** 184 * Returns the combiner associated with the given short name. 185 * 186 * @param name the short name of the combiner type to get. 187 * @return the associated combiner type or null if no matching combiner type is registered. 188 */ 189 public Class<? extends EntitlementCombiner> getCombinerType(String name) { 190 return combiners.get(name); 191 } 192 193 /** 194 * Registers an entitlement subject type with the given short name (used in RESTful API calls and in the UI). 195 * Note: short names must be unique across all subject types. 196 * 197 * @param name the short name of the subject type. 198 * @param type the subject type to register. 199 * @throws NameAlreadyRegisteredException if the short name is already registered. 200 */ 201 public void registerSubjectType(String name, Class<? extends EntitlementSubject> type) { 202 register(name, subjects, type); 203 } 204 205 /** 206 * Registers an entitlement subject type using a short name generated from the type name. The short name is 207 * generated as the simple name of the class minus any {@code Subject} suffix. For example, a subject 208 * type {@code org.forgerock.openam.entitlement.TestSubject} would be registered with the short name {@code Test}. 209 * 210 * @param type the subject type to register. 211 * @throws NameAlreadyRegisteredException if the short name is already registered. 212 */ 213 public void registerSubjectType(Class<? extends EntitlementSubject> type) { 214 String name = getSubjectTypeName(type); 215 registerSubjectType(name, type); 216 } 217 218 /** 219 * Gets the name of the subject type. 220 * @param type The type. 221 * @return The name. 222 */ 223 public static String getSubjectTypeName(Class<? extends EntitlementSubject> type) { 224 return type.getSimpleName().replace("Subject", ""); 225 } 226 227 /** 228 * Returns the subject type associated with the given short name, or null if no such subject is registered. 229 * 230 * @param name the short name of the subject type to get. 231 * @return the associated subject type or null if no matching subject type is registered. 232 */ 233 public Class<? extends EntitlementSubject> getSubjectType(String name) { 234 return subjects.get(name); 235 } 236 237 /** 238 * Registers a resource attribute type with the given short name (used in RESTful API calls and in the UI). 239 * Note: short names must be unique across all resource attribute types. 240 * 241 * @param name the short name of the attribute type. 242 * @param type the attribute type to register. 243 * @throws NameAlreadyRegisteredException if the short name is already registered. 244 */ 245 public void registerAttributeType(String name, Class<? extends ResourceAttribute> type) { 246 register(name, attributes, type); 247 } 248 249 /** 250 * Registers a resource attribute type using a short name generated from the type name. The short name is 251 * generated as the simple name of the class minus any {@code Attribute} suffix. For example, an attribute 252 * type {@code org.forgerock.openam.entitlement.TestAttribute} would be registered with the short name {@code Test}. 253 * 254 * @param type the attribute type to register. 255 * @throws NameAlreadyRegisteredException if the short name is already registered. 256 */ 257 public void registerAttributeType(Class<? extends ResourceAttribute> type) { 258 String name = type.getSimpleName().replace("Attribute", ""); 259 registerAttributeType(name, type); 260 } 261 262 /** 263 * Returns the attribute type associated with the given short name, or null if no such attribute is registered. 264 * 265 * @param name the short name of the attribute type to get. 266 * @return the associated attribute type or null if no matching attribute type is registered. 267 */ 268 public Class<? extends ResourceAttribute> getAttributeType(String name) { 269 return attributes.get(name); 270 } 271 272 /** 273 * Returns the short name that the given condition is registered under. If the condition is registered under 274 * multiple names then an arbitrary name is returned. If the condition type is not registered then null is returned. 275 * 276 * @param condition the condition to get a short name for. 277 * @return the short type name of the given condition or null if not registered. 278 */ 279 public String getConditionName(EntitlementCondition condition) { 280 for (Map.Entry<String, Class<? extends EntitlementCondition>> candidate : conditions.entrySet()) { 281 if (candidate.getValue() == condition.getClass()) { 282 return candidate.getKey(); 283 } 284 } 285 return null; 286 } 287 288 /** 289 * Returns the short name that the given subject is registered under. If the subject is registered under 290 * multiple names then an arbitrary name is returned. If the subject type is not registered then null is returned. 291 * 292 * @param subject the subject to get a short name for. 293 * @return the short type name of the given subject or null if not registered. 294 */ 295 public String getSubjectName(EntitlementSubject subject) { 296 for (Map.Entry<String, Class<? extends EntitlementSubject>> candidate : subjects.entrySet()) { 297 if (candidate.getValue() == subject.getClass()) { 298 return candidate.getKey(); 299 } 300 } 301 return null; 302 } 303 304 /** 305 * Returns the short name that the given attribute is registered under. If the attribute is registered under 306 * multiple names then an arbitrary name is returned. If the attribute type is not registered then null is returned. 307 * 308 * @param attribute the attribute to get a short name for. 309 * @return the short type name of the given attribute or null if not registered. 310 */ 311 public String getAttributeName(ResourceAttribute attribute) { 312 for (Map.Entry<String, Class<? extends ResourceAttribute>> candidate : attributes.entrySet()) { 313 if (candidate.getValue() == attribute.getClass()) { 314 return candidate.getKey(); 315 } 316 } 317 return null; 318 } 319 320 /** 321 * Registers the given type under the given short name in the given registry map. If the map already contains an 322 * entry for this short name and it is not identical to the given type then an exception is thrown and the map is 323 * not updated. 324 * 325 * @param shortName the short name to register the type under. 326 * @param map the map to register the type in. 327 * @param type the type to register. 328 * @param <T> the type of types :-) 329 * @throws NameAlreadyRegisteredException if a different type is already registered under this short name. 330 */ 331 private <T> void register(String shortName, ConcurrentMap<String, Class<? extends T>> map, 332 Class<? extends T> type) { 333 Class<? extends T> previous = map.putIfAbsent(shortName, type); 334 if (previous != null && previous != type) { 335 throw new NameAlreadyRegisteredException(shortName); 336 } 337 } 338 339 /** 340 * Returns all the short names of {@link EntitlementCondition}s currently registered in 341 * this {@link EntitlementRegistry}. 342 * 343 * @return A set of strings containing all the unqiue EntitlementConditions registered at point of query. 344 */ 345 public Set<String> getConditionsShortNames() { 346 return conditions.keySet(); 347 } 348 349 /** 350 * Returns all the short names of {@link EntitlementSubject}s currently registered in 351 * this {@link EntitlementRegistry}. 352 * 353 * @return A set of strings containing all the unqiue EntitlementSubject registered at point of query. 354 */ 355 public Set<String> getSubjectsShortNames() { 356 return subjects.keySet(); 357 } 358 359 /** 360 * Returns all the short names of {@link ResourceAttribute}s currently registered in 361 * this {@link EntitlementRegistry}. 362 * 363 * @return A set of strings containing all the unqiue ResourceAttribute registered at point of query. 364 */ 365 public Set<String> getAttributesShortNames() { 366 return attributes.keySet(); 367 } 368 369 /** 370 * Returns all the short names of {@link EntitlementCombiner}s currently registered in 371 * this {@link EntitlementRegistry}. 372 * 373 * @return A set of strings containing all the unqiue EntitlementCombiners registered at point of query. 374 */ 375 public Set<String> getCombinersShortNames() { 376 return combiners.keySet(); 377 } 378 379 380}