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.el.Bindings.bindings;
021import static org.forgerock.openig.util.JsonValues.asExpression;
022
023import java.util.List;
024import java.util.Map;
025
026import org.forgerock.http.Handler;
027import org.forgerock.http.protocol.Request;
028import org.forgerock.http.protocol.Response;
029import org.forgerock.http.protocol.Status;
030import org.forgerock.http.util.CaseInsensitiveMap;
031import org.forgerock.http.util.MultiValueMap;
032import org.forgerock.json.JsonValue;
033import org.forgerock.openig.el.Bindings;
034import org.forgerock.openig.el.Expression;
035import org.forgerock.openig.heap.GenericHeapObject;
036import org.forgerock.openig.heap.GenericHeaplet;
037import org.forgerock.openig.heap.HeapException;
038import org.forgerock.services.context.Context;
039import org.forgerock.util.promise.NeverThrowsException;
040import org.forgerock.util.promise.Promise;
041import org.forgerock.util.promise.Promises;
042
043/**
044 * Creates a static HTTP response.
045 */
046public class StaticResponseHandler extends GenericHeapObject implements Handler {
047
048    /** The status (code + reason). */
049    private final Status status;
050
051    /** Protocol version (e.g. {@code "HTTP/1.1"}. */
052    private final String version;
053
054    /** Message header fields whose values are expressions that are evaluated. */
055    private final MultiValueMap<String, Expression<String>> headers =
056            new MultiValueMap<>(new CaseInsensitiveMap<List<Expression<String>>>());
057
058    /** The message entity expression. */
059    private final Expression<String> entity;
060
061    /**
062     * Constructor.
063     *
064     * @param status
065     *            The response status to set.
066     */
067    public StaticResponseHandler(final Status status) {
068        this(status, null, null);
069    }
070
071    /**
072     * Constructor.
073     *
074     * @param status
075     *            The response status to set.
076     * @param version
077     *            The protocol version.
078     * @param entity
079     *            The message entity expression.
080     */
081    public StaticResponseHandler(final Status status,
082                                 final String version,
083                                 final Expression<String> entity) {
084        this.status = status;
085        this.version = version;
086        this.entity = entity;
087    }
088
089    /**
090     * Adds a pair key / expression to the header.
091     *
092     * @param key
093     *            The header key.
094     * @param expression
095     *            The expression to evaluate.
096     * @return The current static response handler.
097     */
098    public StaticResponseHandler addHeader(final String key, final Expression<String> expression) {
099        headers.add(key, expression);
100        return this;
101    }
102
103    @Override
104    public Promise<Response, NeverThrowsException> handle(final Context context, final Request request) {
105        Bindings bindings = bindings(context, request);
106        Response response = new Response();
107        response.setStatus(this.status);
108        if (this.version != null) { // default in Message class
109            response.setVersion(this.version);
110        }
111        for (String key : this.headers.keySet()) {
112            for (Expression<String> expression : this.headers.get(key)) {
113                String eval = expression.eval(bindings);
114                if (eval != null) {
115                    response.getHeaders().add(key, eval);
116                }
117            }
118        }
119        if (entity != null) {
120            // use content-type charset (or default)
121            response.setEntity(entity.eval(bindings));
122        }
123        return Promises.newResultPromise(response);
124    }
125
126    /**
127     * Creates and initializes a static response handler in a heap environment.
128     */
129    public static class Heaplet extends GenericHeaplet {
130        @Override
131        public Object create() throws HeapException {
132            final int code = config.get("status").required().asInteger();
133            final String reason = config.get("reason").asString();
134            Status status = Status.valueOf(code, reason);
135            final String version = config.get("version").asString();
136            final JsonValue headers = config.get("headers").expect(Map.class);
137            final Expression<String> entity = asExpression(config.get("entity"), String.class);
138            final StaticResponseHandler handler = new StaticResponseHandler(status, version, entity);
139            if (headers != null) {
140                for (String key : headers.keys()) {
141                    for (JsonValue value : headers.get(key).expect(List.class)) {
142                        handler.addHeader(key, asExpression(value.required(), String.class));
143                    }
144                }
145            }
146            return handler;
147        }
148    }
149}