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 2010–2011 ApexIdentity Inc.
015 * Portions Copyright 2011-2014 ForgeRock AS.
016 */
017
018package org.forgerock.openig.heap;
019
020// TODO: consider detecting cyclic dependencies
021
022import static java.lang.String.*;
023import static java.util.Arrays.*;
024import static java.util.Collections.*;
025import static org.forgerock.json.fluent.JsonValue.*;
026import static org.forgerock.openig.decoration.global.GlobalDecorator.*;
027import static org.forgerock.openig.log.LogSink.*;
028import static org.forgerock.openig.util.Json.*;
029import static org.forgerock.util.Reject.*;
030
031import java.util.ArrayDeque;
032import java.util.ArrayList;
033import java.util.Arrays;
034import java.util.Deque;
035import java.util.HashMap;
036import java.util.LinkedHashMap;
037import java.util.List;
038import java.util.Map;
039
040import org.forgerock.json.fluent.JsonValue;
041import org.forgerock.json.fluent.JsonValueException;
042import org.forgerock.openig.decoration.Context;
043import org.forgerock.openig.decoration.Decorator;
044import org.forgerock.openig.decoration.global.GlobalDecorator;
045import org.forgerock.openig.handler.Handler;
046import org.forgerock.openig.log.LogSink;
047import org.forgerock.openig.log.Logger;
048import org.forgerock.openig.util.MultiValueMap;
049
050/**
051 * The concrete implementation of a heap. Provides methods to initialize and destroy a heap.
052 * A Heap can be part of a heap hierarchy: if the queried object is not found locally, and if it has a parent,
053 * the parent will be queried (and this, recursively until there is no parent anymore).
054 */
055public class HeapImpl implements Heap {
056
057    /**
058     * List of attributes that should be ignored when looking for decorator's configuration.
059     */
060    private static final List<String> EXCLUDED_ATTRIBUTES = asList("type", "name", "config");
061
062    /**
063     * Parent heap to delegate queries to if nothing is found in the local heap.
064     * It may be null if this is the root heap (built by the system).
065     */
066    private final HeapImpl parent;
067
068    /**
069     * Heap name.
070     */
071    private final Name name;
072
073    /** Heaplets mapped to heaplet identifiers in the heap configuration. */
074    private Map<String, Heaplet> heaplets = new HashMap<String, Heaplet>();
075
076    /** Configuration objects for heaplets. */
077    private Map<String, JsonValue> configs = new HashMap<String, JsonValue>();
078
079    /** Objects allocated in the heap mapped to heaplet names. */
080    private Map<String, Object> objects = new HashMap<String, Object>();
081
082    /** Per-heaplet decoration contexts mapped to heaplet names. */
083    private Map<String, Context> contexts = new HashMap<String, Context>();
084
085    /** Per-heaplet decoration(s) mapped to heaplet names. */
086    private MultiValueMap<String, JsonValue> decorations =
087            new MultiValueMap<String, JsonValue>(new LinkedHashMap<String, List<JsonValue>>());
088
089    /**
090     * Decorator for the 'main handler' reference.
091     */
092    private Decorator topLevelHandlerDecorator;
093
094    /**
095     * Top-level 'handler' reference decorations (effectively the root node of configuration).
096     */
097    private JsonValue config;
098
099    /**
100     * Heap logger.
101     */
102    private Logger logger;
103
104    /**
105     * Keep track of objects being resolved, used to avoid recursive issues.
106     */
107    private Deque<String> resolving = new ArrayDeque<String>();
108
109    /**
110     * List of default object declarations to be inserted in this heap if no user-provided objects were found.
111     */
112    private List<JsonValue> defaults = new ArrayList<JsonValue>();
113
114    /**
115     * Builds an anonymous root heap (will be referenced by children but has no parent itself).
116     * Intended for tests only.
117     */
118    HeapImpl() {
119        this((HeapImpl) null);
120    }
121
122    /**
123     * Builds a new anonymous heap that is a child of the given heap.
124     * Intended for tests only.
125     *
126     * @param parent
127     *         parent heap.
128     */
129    HeapImpl(final HeapImpl parent) {
130        this(parent, Name.of("anonymous"));
131    }
132
133    /**
134     * Builds a root heap (will be referenced by children but has no parent itself).
135     *
136     * @param name
137     *         local name of this heap
138     */
139    public HeapImpl(final Name name) {
140        this(null, name);
141    }
142
143    /**
144     * Builds a new heap that is a child of the given heap.
145     *
146     * @param parent
147     *         parent heap.
148     * @param name
149     *         local name of this heap
150     */
151    public HeapImpl(final HeapImpl parent, final Name name) {
152        this.parent = parent;
153        this.name = checkNotNull(name);
154    }
155
156    /**
157     * Initializes the heap using the given configuration. Once complete, all heaplets will
158     * be loaded and all associated objects are allocated using each heaplet instance's
159     * configuration.
160     *
161     * @param config the configuration root.
162     * @param reservedFieldNames the names of reserved top level fields in the config which
163     *                           should not be parsed as global decorators.
164     * @throws HeapException if an exception occurs allocating heaplets.
165     * @throws JsonValueException if the configuration object is malformed.
166     */
167    public synchronized void init(JsonValue config, String... reservedFieldNames)
168            throws HeapException {
169        // process configuration object model structure
170        this.config = config;
171        boolean logDeprecationWarning = false;
172        JsonValue heap = config.get("heap").defaultTo(emptyList());
173        if (heap.isMap()) {
174            /*
175             * In OpenIG < 3.1 the heap objects were listed in a child "objects"
176             * array. The extra nesting was found to be redundant and removed in
177             * 3.1. We continue to allow it in order to maintain backwards
178             * compatibility.
179             */
180            heap = heap.get("objects").required();
181
182            // We cannot log anything just yet because the heap is not initialized.
183            logDeprecationWarning = true;
184        }
185        for (JsonValue object : heap.expect(List.class)) {
186            addDeclaration(object);
187        }
188
189        // Register the default objects if the user has not overridden them
190        // Notice this will only verify objects declared in the heap, not the inline declarations
191        for (JsonValue value : defaults) {
192            String name = value.get("name").required().asString();
193            if (heaplets.get(name) == null) {
194                addDeclaration(value);
195            }
196            // otherwise the object configuration has been overridden
197        }
198
199        // register global decorators, ensuring that reserved field names are filtered out
200        int sz = reservedFieldNames.length;
201        String[] allReservedFieldNames = Arrays.copyOf(reservedFieldNames, sz + 1);
202        allReservedFieldNames[sz] = "heap";
203        topLevelHandlerDecorator = new GlobalDecorator(null, config, allReservedFieldNames);
204
205        if (config.isDefined("globalDecorators")) {
206            Decorator parentGlobalDecorator =
207                    parent != null ? parent.get(GLOBAL_DECORATOR_HEAP_KEY, Decorator.class) : null;
208            put(GLOBAL_DECORATOR_HEAP_KEY,
209                new GlobalDecorator(parentGlobalDecorator, config.get("globalDecorators").expect(Map.class)));
210        }
211
212        // instantiate all objects, recursively allocating dependencies
213        for (String name : new ArrayList<String>(heaplets.keySet())) {
214            get(name, Object.class);
215        }
216
217        // We can log a warning now that the heap is initialized.
218        logger = new Logger(resolve(config.get("logSink").defaultTo(LOGSINK_HEAP_KEY),
219                                           LogSink.class,
220                                           true), name);
221        if (logDeprecationWarning) {
222            logger.warning("The configuration field heap/objects has been deprecated. Heap objects "
223                                   + "should now be listed directly in the top level \"heap\" field, "
224                                   + "e.g. { \"heap\" : [ objects... ] }.");
225        }
226    }
227
228    /**
229     * Add a default JsonValue object declaration in this heap.
230     *
231     * This method should only be called prior to {@link #init(JsonValue, String...)}: values provided after init() has
232     * been called will be ignored.
233     *
234     * Notice that if the caller add 2 declarations with the same name, that will ends up with an exception thrown
235     * when heap will be initialized.
236     *
237     * @param object
238     *         Object declaration (has to contains a {@literal name} attribute)
239     */
240    public void addDefaultDeclaration(final JsonValue object) {
241        defaults.add(object);
242    }
243
244    /**
245     * Add the given JsonValue as a new object declaration in this heap. The given object must be a valid object
246     * declaration ({@literal name}, {@literal type} and {@literal config} attributes). If not, a JsonValueException
247     * will be thrown. After this method is called, a new object is available in the heap.
248     *
249     * <p>The {@literal config} attribute is optional and accordingly, if empty or null, its declaration can be omitted.
250     *
251     * @param object
252     *         object declaration to add to the heap.
253     */
254    private void addDeclaration(final JsonValue object) {
255        object.required().expect(Map.class);
256        Heaplet heaplet = Heaplets.getHeaplet(asClass(object.get("type").required()));
257        if (heaplet == null) {
258            throw new JsonValueException(object.get("type"), "no heaplet available to initialize object");
259        }
260        // objects[n].name (string)
261        String name = object.get("name").required().asString();
262        if (heaplets.get(name) != null) {
263            throw new JsonValueException(object.get("name"), "object already defined");
264        }
265        // remove pre-allocated objects to be replaced
266        objects.remove(name);
267        heaplets.put(name, heaplet);
268        // objects[n].config (object)
269        configs.put(name, object.get("config").defaultTo(emptyMap()).expect(Map.class));
270        // Store decorations
271        for (JsonValue candidate : object) {
272            // Exclude standard declaration elements
273            if (!EXCLUDED_ATTRIBUTES.contains(candidate.getPointer().leaf())) {
274                decorations.add(name, candidate);
275            }
276        }
277    }
278
279    @Override
280    public <T> T get(final String name, final Class<T> type) throws HeapException {
281        ExtractedObject extracted = extract(name);
282        if (extracted.object == null) {
283            return null;
284        }
285        return type.cast(applyGlobalDecorations(extracted));
286    }
287
288    /**
289     * Extract the given named heap object from this heap or its parent (if any).
290     * The returned pointer is never {@code null} but can contains {@code null} {@literal object} and {@literal
291     * context}.
292     * The {@literal object} reference has only be decorated with it's locally (per-heaplet) defined decorations.
293     * This is the responsibility of the initial 'requester' heap (the one that have its {@link #get(String, Class)}
294     * method called in the first place) to complete the decoration with global decorators.
295     *
296     * @param name heap object name
297     * @return an {@link ExtractedObject} pair-like structure that may contains both the heap object and its
298     * associated context (never returns {@code null})
299     * @throws HeapException if extraction failed
300     */
301    ExtractedObject extract(final String name) throws HeapException {
302        if (resolving.contains(name)) {
303            // Fail for recursive object resolution
304            throw new HeapException(
305                    format("Object '%s' is already being constructed (probably a duplicate name in configuration)",
306                           name));
307        }
308        Object object = objects.get(name);
309        if (object == null) {
310            Heaplet heaplet = heaplets.get(name);
311            if (heaplet != null) {
312                try {
313                    resolving.push(name);
314                    JsonValue configuration = configs.get(name);
315                    object = heaplet.create(this.name.child(name), configuration, this);
316                    if (object == null) {
317                        throw new HeapException(new NullPointerException());
318                    }
319                    object = applyObjectLevelDecorations(name, object, configuration);
320                    put(name, object);
321                } finally {
322                    resolving.pop();
323                }
324            } else if (parent != null) {
325                // no heaplet available, query parent (if any)
326                return parent.extract(name);
327            }
328        }
329        return new ExtractedObject(object, contexts.get(name));
330    }
331
332    @Override
333    public <T> T resolve(final JsonValue reference, final Class<T> type) throws HeapException {
334        return resolve(reference, type, false);
335    }
336
337    @Override
338    public <T> T resolve(final JsonValue reference, final Class<T> type, final boolean optional) throws HeapException {
339
340        // If optional if set, accept that the provided reference may wrap a null
341        if (optional && reference.isNull()) {
342            return null;
343        }
344
345        // Otherwise we require a value
346        JsonValue required = reference.required();
347        if (required.isString()) {
348            // handle named reference
349            T value = get(required.asString(), type);
350            if (value == null) {
351                throw new JsonValueException(reference, "Object " + reference.asString() + " not found in heap");
352            }
353            return value;
354        } else if (required.isMap()) {
355            // handle inline declaration
356            String generated = name(required);
357
358            // when resolve() is called multiple times with the same reference, this prevent "already registered" errors
359            T value = get(generated, type);
360            if (value == null) {
361                // First resolution
362                required.put("name", generated);
363                addDeclaration(required);
364                // Get decorated object
365                value = get(generated, type);
366                if (value == null) {
367                    // Very unlikely to happen
368                    throw new JsonValueException(reference, "Reference is not a valid heap object");
369                }
370            }
371            return value;
372        }
373        throw new JsonValueException(reference,
374                                     format("JsonValue[%s] is neither a String nor a Map (inline declaration)",
375                                            reference.getPointer()));
376    }
377
378    /**
379     * Infer a locally unique name for the given object declaration.
380     * If a {@literal name} attribute is provided, simply return its value as name, otherwise compose a
381     * unique name composed of both the declaration JSON pointer (map to the location within the JSON file) and
382     * the value of the {@literal type} attribute (ease to identify the object).
383     * <p>
384     * The following declaration would return {@literal Inline}:
385     * <pre>
386     *     {@code
387     *     {
388     *         "name": "Inline",
389     *         "type": "Router"
390     *     }
391     *     }
392     * </pre>
393     * <p>
394     * And this one would return {@literal {WelcomeHandler}/heap/objects/0/config/defaultHandler}:
395     * <pre>
396     *     {@code
397     *     {
398     *         "type": "WelcomeHandler"
399     *     }
400     *     }
401     * </pre>
402     * @param declaration source inline object declaration
403     * @return a locally unique name
404     */
405    public static String name(final JsonValue declaration) {
406        JsonValue node = declaration.get("name");
407        if (node.isNull()) {
408            String location = declaration.getPointer().toString();
409            String type = declaration.get("type").required().asString();
410            return format("{%s}%s", type, location);
411        }
412        return node.asString();
413    }
414
415    /**
416     * Puts an object into the heap. If an object already exists in the heap with the
417     * specified name, it is overwritten.
418     *
419     * @param name name of the object to be put into the heap.
420     * @param object the object to be put into the heap.
421     */
422    public synchronized void put(final String name, final Object object) {
423        objects.put(name, object);
424        contexts.put(name, new DecorationContext(this, this.name.child(name), json(emptyMap())));
425    }
426
427    /**
428     * Decorates the given extracted heap object with global decorators.
429     *
430     * @return the extracted object (only decorated with its local decorations).
431     * @throws HeapException
432     *         if a decorator failed to apply
433     */
434    private Object applyGlobalDecorations(final ExtractedObject extracted) throws HeapException {
435
436        // Fast exit if there is nothing to decorate
437        if (extracted.object == null) {
438            return null;
439        }
440
441        // Starts with the original object
442        Object decorated = extracted.object;
443
444        // Avoid decorating decorators themselves
445        // Avoid StackOverFlow Exceptions because of infinite recursion
446        if (decorated instanceof Decorator) {
447            return decorated;
448        }
449
450        // Apply global decorations (may be inherited from parent heap)
451        ExtractedObject deco = extract(GLOBAL_DECORATOR_HEAP_KEY);
452        if (deco.object != null) {
453            Decorator globalDecorator = (Decorator) deco.object;
454            decorated = globalDecorator.decorate(decorated, null, extracted.context);
455        }
456
457        return decorated;
458    }
459
460    /**
461     * Decorates the given (local) heap object instance with its own decorations (decorations within the heap object
462     * declaration).
463     *
464     * @param name
465     *         name of the heap object
466     * @param object
467     *         delegate (to be decorated) instance
468     * @param configuration
469     *         heap object configuration
470     * @return the decorated instance (may be null if delegate object is null)
471     * @throws HeapException
472     *         when we cannot get a declared decorator from the heap
473     */
474    private Object applyObjectLevelDecorations(final String name,
475                                               final Object object,
476                                               final JsonValue configuration) throws HeapException {
477        Object decorated = object;
478
479        // Avoid decorating decorators themselves
480        // Avoid StackOverFlow Exceptions because of infinite recursion
481        if (decorated instanceof Decorator) {
482            return decorated;
483        }
484
485        // Create a context object for holding shared values
486        Context context = new DecorationContext(this,
487                                                this.name.child(name),
488                                                configuration);
489        // .. and save it for later use in extract()
490        contexts.put(name, context);
491
492        if (decorations.containsKey(name)) {
493            // We have decorators for this instance, try to apply them
494            for (JsonValue decoration : decorations.get(name)) {
495
496                // The element name is the decorator heap object name
497                String decoratorName = decoration.getPointer().leaf();
498                Decorator decorator = get(decoratorName, Decorator.class);
499                if (decorator != null) {
500                    // We just ignore when no named decorator is found
501                    // TODO Keep a list of intermediate objects for later use
502                    decorated = decorator.decorate(decorated, decoration, context);
503                }
504            }
505        }
506        return decorated;
507    }
508
509    /**
510     * Destroys the objects on the heap and dereferences all associated objects. This method
511     * calls the heaplet {@code destroy} method for each object in the heap to provide a
512     * chance for system resources to be freed.
513     */
514    public synchronized void destroy() {
515        // save the heaplets locally to send destroy notifications
516        Map<String, Heaplet> h = heaplets;
517        // prevent any further (inadvertent) object allocations
518        heaplets = new HashMap<String, Heaplet>();
519        // all allocated objects are no longer in this heap
520        objects.clear();
521        // iterate through saved heaplets, notifying about destruction
522        for (String name : h.keySet()) {
523            h.get(name).destroy();
524        }
525        contexts.clear();
526
527    }
528
529    /**
530     * Returns the {@link Handler} object referenced by the {@literal handler} top-level attribute.
531     * The returned object is fully decorated (including top-level reference decorations).
532     *
533     * @return the {@link Handler} object referenced by the {@literal handler} top-level attribute
534     * @throws HeapException if object resolution failed
535     */
536    public Handler getHandler() throws HeapException {
537        JsonValue reference = getWithDeprecation(config, logger, "handler", "handlerObject");
538        Handler handler = resolve(reference, Handler.class);
539        // FIXME: how to grab the decoration context of this object (it may not origin from this heap) ?
540        DecorationContext context = new DecorationContext(this,
541                                                          name.child("top-level-handler"),
542                                                          json(emptyMap()));
543        return (Handler) topLevelHandlerDecorator.decorate(handler, null, context);
544    }
545
546    /**
547     * A simple data holder object letting decorators known about the decorated heap object.
548     */
549    private class DecorationContext implements Context {
550        private final Heap heap;
551        private final Name name;
552        private final JsonValue config;
553
554        public DecorationContext(final Heap heap,
555                                 final Name name,
556                                 final JsonValue config) {
557            this.heap = heap;
558            this.name = name;
559            this.config = config;
560        }
561
562        @Override
563        public Heap getHeap() {
564            return heap;
565        }
566
567        @Override
568        public Name getName() {
569            return name;
570        }
571
572        @Override
573        public JsonValue getConfig() {
574            return config;
575        }
576    }
577
578    /**
579     * Pair object containing both the retrieved heap object instance with its origin decoration context.
580     */
581    private static class ExtractedObject {
582        Object object;
583        Context context;
584
585        /**
586         * Builds a new pair object.
587         *
588         * @param object object instance (may be null)
589         * @param context decoration context (may be null)
590         */
591        public ExtractedObject(final Object object, final Context context) {
592            this.object = object;
593            this.context = context;
594        }
595    }
596}