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.handler;
019
020import static org.forgerock.openig.util.JsonValues.asExpression;
021
022import java.util.ArrayDeque;
023import java.util.ArrayList;
024import java.util.Deque;
025import java.util.List;
026import java.util.Map;
027
028import org.forgerock.http.Handler;
029import org.forgerock.http.protocol.Request;
030import org.forgerock.http.protocol.Response;
031import org.forgerock.json.JsonValue;
032import org.forgerock.openig.el.Bindings;
033import org.forgerock.openig.el.Expression;
034import org.forgerock.openig.heap.GenericHeapObject;
035import org.forgerock.openig.heap.GenericHeaplet;
036import org.forgerock.openig.heap.HeapException;
037import org.forgerock.services.context.Context;
038import org.forgerock.util.promise.NeverThrowsException;
039import org.forgerock.util.promise.Promise;
040import org.forgerock.util.promise.PromiseImpl;
041import org.forgerock.util.promise.ResultHandler;
042
043/**
044 * Processes a request through a sequence of handlers. This allows multi-request processing such as retrieving a form,
045 * extracting form content (e.g. nonce) and submitting in a subsequent request.
046 */
047public class SequenceHandler extends GenericHeapObject implements Handler {
048
049    /** Handlers and associated sequence processing postconditions. */
050    private final List<Binding> bindings = new ArrayList<>();
051
052    /**
053     * Binds sequenced handlers with sequence processing postconditions.
054     *
055     * @param handler
056     *            The name of the handler heap object to dispatch to if the associated condition yields true.
057     * @param postcondition
058     *            evaluated to determine if sequence continues (default: {@code null} a.k.a. unconditional)
059     * @return The current dispatch handler.
060     */
061    public SequenceHandler addBinding(final Handler handler, final Expression<Boolean> postcondition) {
062        bindings.add(new Binding(handler, postcondition));
063        return this;
064    }
065
066    @Override
067    public Promise<Response, NeverThrowsException> handle(final Context context, final Request request) {
068
069        final PromiseImpl<Response, NeverThrowsException> composite = PromiseImpl.create();
070
071        final Deque<Binding> theBindings = new ArrayDeque<>(bindings);
072
073        Binding binding = theBindings.peekFirst();
074        Promise<Response, NeverThrowsException> promise = binding.handler.handle(context, request);
075        promise.thenOnResult(new ResultHandler<Response>() {
076
077            @Override
078            public void handleResult(final Response result) {
079                Binding binding = theBindings.removeFirst();
080                Bindings scope = Bindings.bindings(context, request, result);
081                if ((binding.postcondition != null && !Boolean.TRUE.equals(binding.postcondition.eval(scope)))
082                        || theBindings.isEmpty()) {
083                    // Do not continue
084                    composite.handleResult(result);
085                } else {
086                    // Next promise
087                    final Binding next = theBindings.peekFirst();
088                    next.handler.handle(context, request)
089                                .thenOnResult(this);
090
091                }
092            }
093        });
094
095        return composite;
096    }
097
098    /** Binds sequenced handlers with sequence processing postconditions. */
099    private static class Binding {
100
101        private final Handler handler;
102
103        private final Expression<Boolean> postcondition;
104
105        /**
106         * Default constructor.
107         *
108         * @param handler
109         *            Handler to dispatch request to.
110         * @param postcondition
111         *            Postcondition evaluated to determine if sequence continues (default: {@code null} a.k.a.
112         *            unconditional).
113         */
114        Binding(Handler handler, Expression<Boolean> postcondition) {
115            this.handler = handler;
116            this.postcondition = postcondition;
117        }
118    }
119
120    /** Creates and initializes a sequence handler in a heap environment. */
121    public static class Heaplet extends GenericHeaplet {
122        @Override
123        public Object create() throws HeapException {
124            final SequenceHandler sequenceHandler = new SequenceHandler();
125            for (final JsonValue jv : config.get("bindings").required().expect(List.class)) {
126                jv.required().expect(Map.class);
127                final Handler handler = heap.resolve(jv.get("handler"), Handler.class);
128                final Expression<Boolean> postcondition = asExpression(jv.get("postcondition"), Boolean.class);
129                sequenceHandler.addBinding(handler, postcondition);
130            }
131            return sequenceHandler;
132        }
133    }
134}