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-2015 ForgeRock AS.
016 */
017
018package org.forgerock.openig.heap;
019
020import static java.lang.String.format;
021import static org.forgerock.http.routing.RouteMatchers.requestUriMatcher;
022import static org.forgerock.http.routing.RoutingMode.EQUALS;
023import static org.forgerock.openig.heap.Keys.ENDPOINT_REGISTRY_HEAP_KEY;
024import static org.forgerock.openig.heap.Keys.LOGSINK_HEAP_KEY;
025import static org.forgerock.openig.heap.Keys.TEMPORARY_STORAGE_HEAP_KEY;
026import static org.forgerock.openig.util.StringUtil.slug;
027
028import java.util.Arrays;
029import java.util.HashSet;
030import java.util.Map;
031import java.util.Set;
032
033import org.forgerock.http.routing.Router;
034import org.forgerock.json.JsonValue;
035import org.forgerock.openig.handler.Handlers;
036import org.forgerock.openig.http.EndpointRegistry;
037import org.forgerock.openig.io.TemporaryStorage;
038import org.forgerock.openig.log.LogSink;
039import org.forgerock.openig.log.Logger;
040
041/**
042 * A generic base class for heaplets with automatically injected fields.
043 * <p>
044 * If the object created is an instance of {@link GenericHeapObject}, it is then
045 * automatically injected with {@code logger} and {@code storage} objects.
046 */
047public abstract class GenericHeaplet implements Heaplet {
048
049    /** Heap objects to avoid dependency injection (prevents circular dependencies). */
050    private static final Set<String> SPECIAL_OBJECTS =
051            new HashSet<>(Arrays.asList(LOGSINK_HEAP_KEY, TEMPORARY_STORAGE_HEAP_KEY));
052
053    /** The name of the object to be created and stored in the heap by this heaplet. */
054    protected String name;
055
056    /** The fully qualified name of the object to be created. */
057    protected Name qualified;
058
059    /** The heaplet's object configuration object. */
060    protected JsonValue config;
061
062    /** Where objects should be put and where object dependencies should be retrieved. */
063    protected Heap heap;
064
065    /** Provides methods for logging activities. */
066    protected Logger logger;
067
068    /** Allocates temporary buffers for caching streamed content during processing. */
069    protected TemporaryStorage storage;
070
071    /** The object created by the heaplet's {@link #create()} method. */
072    protected Object object;
073    private EndpointRegistry.Registration registration;
074    private EndpointRegistry registry;
075
076    @Override
077    public Object create(Name name, JsonValue config, Heap heap) throws HeapException {
078        this.name = name.getLeaf();
079        this.qualified = name;
080        this.config = config.required().expect(Map.class);
081        this.heap = heap;
082        if (!SPECIAL_OBJECTS.contains(this.name)) {
083            this.logger = new Logger(
084                    heap.resolve(
085                            config.get("logSink").defaultTo(LOGSINK_HEAP_KEY),
086                            LogSink.class, true),
087                    name);
088            this.storage = heap.resolve(
089                    config.get("temporaryStorage").defaultTo(TEMPORARY_STORAGE_HEAP_KEY),
090                    TemporaryStorage.class);
091        }
092        this.object = create();
093        if (this.object instanceof GenericHeapObject) {
094            // instrument object if possible
095            GenericHeapObject ghObject = (GenericHeapObject) this.object;
096            ghObject.logger = this.logger;
097            ghObject.storage = this.storage;
098        }
099        start();
100        return object;
101    }
102
103    /**
104     * Returns this object's {@link EndpointRegistry}, creating it lazily when requested for the first time.
105     *
106     * @return this object's {@link EndpointRegistry} ({@literal /objects/[name]})
107     * @throws HeapException
108     *         should never be thrown
109     */
110    protected EndpointRegistry endpointRegistry() throws HeapException {
111        if (registry == null) {
112            // Get parent registry (.../objects)
113            EndpointRegistry parent = heap.get(ENDPOINT_REGISTRY_HEAP_KEY, EndpointRegistry.class);
114            Router router = new Router();
115            router.addRoute(requestUriMatcher(EQUALS, ""), Handlers.NO_CONTENT);
116            String objectName = qualified.getLeaf();
117            String slug = slug(objectName);
118            if (!slug.equals(objectName)) {
119                logger.warning(format("Heaplet name ('%s') has been converted to "
120                                              + "a slug ('%s') for URL exposition (REST endpoints).",
121                                      objectName,
122                                      slug));
123            }
124            registration = parent.register(slug, router);
125            registry = new EndpointRegistry(router, registration.getPath());
126        }
127        return registry;
128    }
129
130    @Override
131    public void destroy() {
132        if (registration != null) {
133            registration.unregister();
134        }
135    }
136
137    /**
138     * Called to request the heaplet create an object. Called by
139     * {@link Heaplet#create(Name, JsonValue, Heap)} after initializing
140     * the protected field members. Implementations should parse configuration
141     * but not acquire resources, start threads, or log any initialization
142     * messages. These tasks should be performed by the {@link #start()} method.
143     *
144     * @return The created object.
145     * @throws HeapException
146     *             if an exception occurred during creation of the heap object
147     *             or any of its dependencies.
148     * @throws org.forgerock.json.JsonValueException
149     *             if the heaplet (or one of its dependencies) has a malformed
150     *             configuration.
151     */
152    public abstract Object create() throws HeapException;
153
154    /**
155     * Called to request the heaplet start an object. Called by
156     * {@link Heaplet#create(Name, JsonValue, Heap)} after creating and
157     * configuring the object and once the object's logger and storage have been
158     * configured. Implementations should override this method if they need to
159     * acquire resources, start threads, or log any initialization messages.
160     *
161     * @throws HeapException
162     *             if an exception occurred while starting the heap object or
163     *             any of its dependencies.
164     */
165    public void start() throws HeapException {
166        // default does nothing
167    }
168}