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}