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 2015 ForgeRock AS. 015 * 016 */ 017 018package org.forgerock.openig.el; 019 020import static java.util.Collections.unmodifiableMap; 021 022import java.util.HashMap; 023import java.util.LinkedHashMap; 024import java.util.Map; 025 026import org.forgerock.http.protocol.Request; 027import org.forgerock.http.protocol.Response; 028import org.forgerock.http.session.SessionContext; 029import org.forgerock.services.context.AttributesContext; 030import org.forgerock.services.context.Context; 031import org.forgerock.util.Reject; 032 033/** 034 * Bindings represents the Expression bindings used during evaluation and assignation. 035 */ 036public class Bindings { 037 038 private final Map<String, Object> map = new LinkedHashMap<>(); 039 040 /** 041 * Returns an empty {@link Bindings} instance (mutable). 042 * 043 * @return an empty {@link Bindings} instance. 044 */ 045 public static Bindings bindings() { 046 return new Bindings(); 047 } 048 049 /** 050 * Returns a {@link Bindings} initialized with the given {@code context} and {@code request}. 051 * 052 * <p>The returned bindings contain a {@code contexts} entry that provides easy access to visible parent 053 * Contexts ({@code contexts.http, contexts.client, ...}). 054 * 055 * <p>They also give access to the request's {@code attributes} from the 056 * {@link org.forgerock.services.context.AttributesContext} and to the {@code session} 057 * from the {@link org.forgerock.http.session.SessionContext}. 058 * 059 * @param context 060 * The context to expose 061 * @param request 062 * The request to expose 063 * @return an initialized {@link Bindings} instance. 064 */ 065 public static Bindings bindings(Context context, Request request) { 066 Bindings bindings = bindings() 067 .bind("context", context) 068 .bind("request", request); 069 if (context != null) { 070 bindings.bind("contexts", flatten(context)); 071 if (context.containsContext(AttributesContext.class)) { 072 bindings.bind("attributes", context.asContext(AttributesContext.class).getAttributes()); 073 } 074 if (context.containsContext(SessionContext.class)) { 075 bindings.bind("session", context.asContext(SessionContext.class).getSession()); 076 } 077 } 078 return bindings; 079 } 080 081 /** 082 * Returns a {@link Bindings} initialized with the given {@code context}, {@code request} and {@code response}. 083 * 084 * @param context 085 * The context to expose 086 * @param request 087 * The request to expose 088 * @param response 089 * The response to expose 090 * @return an initialized {@link Bindings} instance. 091 */ 092 public static Bindings bindings(Context context, Request request, Response response) { 093 return bindings(context, request) 094 .bind("response", response); 095 } 096 097 /** 098 * Returns a singleton {@link Bindings} initialized with the given {@code name} and {@code value}. 099 * 100 * @param name 101 * binding name 102 * @param value 103 * binding value 104 * @return an initialized {@link Bindings} instance. 105 */ 106 public static Bindings bindings(final String name, final Object value) { 107 return bindings().bind(name, value); 108 } 109 110 /** 111 * Binds a new {@code value} to the given {@code name}. 112 * 113 * @param name 114 * binding name (must not be {@code null}) 115 * @param value 116 * binding value 117 * @return this {@link Bindings} 118 */ 119 public Bindings bind(String name, Object value) { 120 Reject.ifNull(name); 121 map.put(name, value); 122 return this; 123 } 124 125 /** 126 * Binds all the bindings already bound from {@code source}. 127 * 128 * @param source 129 * current bindings to copy (must not be {@code null}) 130 * @return this {@link Bindings} 131 */ 132 public Bindings bind(Bindings source) { 133 Reject.ifNull(source); 134 map.putAll(source.map); 135 return this; 136 } 137 138 /** 139 * Returns an unmodifiable {@code Map} view of this {@code Bindings} instance. 140 * <p> 141 * Note that while the consumer of the Map cannot modify it, any changes done through the Bindings methods 142 * will be reflected in the returned Map. 143 * 144 * @return an unmodifiable {@code Map} view of this instance (never {@code null}). 145 */ 146 public Map<String, Object> asMap() { 147 return unmodifiableMap(map); 148 } 149 150 /** 151 * Flatten the current {@code leaf} {@link Context} into a Map keyed by context name. 152 * 153 * <p> 154 * The Context hierarchy is walked from the given {@code leaf} to the root context, parent after parent. 155 * 156 * <p> 157 * If a context's name has already been added into the Map while walking up the chain, the context will be 158 * ignored (shadowed by the previously registered context). That behaviour ensure that we'll return only the 159 * contexts that are close to the leaf. 160 * 161 * @param leaf 162 * Context used to start the walk-through 163 * @return a Map of context 164 * @see Context#getContextName() 165 */ 166 public static Map<String, Context> flatten(Context leaf) { 167 Map<String, Context> contexts = new HashMap<>(); 168 contexts.put(leaf.getContextName(), leaf); 169 170 Context context = leaf; 171 while (context.getParent() != null) { 172 context = context.getParent(); 173 String name = context.getContextName(); 174 if (!contexts.containsKey(name)) { 175 // Ignore already mapped contexts 176 contexts.put(name, context); 177 } 178 } 179 180 return contexts; 181 } 182 183 @Override 184 public String toString() { 185 return map.toString(); 186 } 187 188}