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 2012-2015 ForgeRock AS. 015 */ 016 017package org.forgerock.services.context; 018 019import static org.forgerock.json.JsonValue.json; 020import static org.forgerock.json.JsonValue.object; 021 022import java.lang.reflect.Constructor; 023 024import org.forgerock.json.JsonValue; 025import org.forgerock.util.Reject; 026 027/** 028 * A base implementation of the {@link Context} interface. Derived Contexts <b>MUST</b> support persistence by providing 029 * <ul> 030 * <li>a <b>public</b> constructor having the same declaration as 031 * {@link #AbstractContext(JsonValue, ClassLoader)}</li> 032 * <li>a <b>public</b> method having the same declaration as 033 * {@link Context#toJsonValue}</li> 034 * </ul> 035 * See the documentation for more details. 036 * <p> 037 * Here is an example of the JSON representation of the core attributes of all 038 * contexts: 039 * 040 * <pre> 041 * { 042 * "id" : "56f0fb7e-3837-464d-b9ec-9d3b6af665c3", 043 * "class" : ...Java class name.., 044 * "parent" : { 045 * ... 046 * } 047 * } 048 * </pre> 049 */ 050public abstract class AbstractContext implements Context { 051 052 // Persisted attribute names. 053 private static final String ATTR_CLASS = "class"; 054 private static final String ATTR_ID = "id"; 055 private static final String ATTR_NAME = "name"; 056 private static final String ATTR_PARENT = "parent"; 057 058 /** 059 * The parent Context. 060 */ 061 private final Context parent; 062 063 /** 064 * The Context data. 065 */ 066 protected final JsonValue data; 067 068 /** 069 * Constructs a new {@code AbstractContext} with a {@code null} {@code id}. 070 * 071 * @param parent The parent context. 072 * @param name The name of the context. 073 */ 074 protected AbstractContext(Context parent, String name) { 075 this(null, name, parent); 076 } 077 078 /** 079 * Constructs a new {@code AbstractContext}. 080 * 081 * @param id The id of the context. 082 * @param parent The parent context. 083 * @param name The name of the context. 084 */ 085 protected AbstractContext(String id, String name, Context parent) { 086 data = json(object()); 087 data.put(ATTR_CLASS, getClass().getName()); 088 if (id != null) { 089 data.put(ATTR_ID, id); 090 } 091 data.put(ATTR_NAME, name); 092 this.parent = parent; 093 } 094 095 /** 096 * Creates a new context from the JSON representation of a previously 097 * persisted context. 098 * <p> 099 * Sub-classes <b>MUST</b> provide a constructor having the same declaration 100 * as this constructor in order to support persistence. Implementations 101 * <b>MUST</b> take care to invoke the super class implementation before 102 * parsing their own context attributes. Below is an example implementation 103 * for a security context which stores the user name and password of the 104 * authenticated user: 105 * 106 * <pre> 107 * protected SecurityContext(JsonValue savedContext, ClassLoader classLoader) { 108 * // Invoke the super-class implementation first. 109 * super(savedContext, classLoader); 110 * 111 * // Now parse the attributes for this context. 112 * this.username = savedContext.get("username").required().asString(); 113 * this.password = savedContext.get("password").required().asString(); 114 * } 115 * </pre> 116 * 117 * In order to create a context's persisted JSON representation, 118 * implementations must override. 119 * 120 * @param savedContext 121 * The JSON representation from which this context's attributes 122 * should be parsed. 123 * @param classLoader 124 * The ClassLoader which can properly resolve the persisted class-name. 125 */ 126 public AbstractContext(final JsonValue savedContext, final ClassLoader classLoader) { 127 final JsonValue savedParentContext = savedContext.get(ATTR_PARENT); 128 savedContext.remove(ATTR_PARENT); 129 data = savedContext.copy(); 130 this.parent = savedParentContext.isNull() ? null : load0(savedParentContext, classLoader); 131 } 132 133 private static Context load0(final JsonValue savedContext, final ClassLoader classLoader) { 134 // Determine the context implementation class and instantiate it. 135 final String className = savedContext.get(ATTR_CLASS).required().asString(); 136 try { 137 final Class<? extends Context> clazz = Class.forName(className, true, classLoader) 138 .asSubclass(AbstractContext.class); 139 final Constructor<? extends Context> constructor = clazz.getDeclaredConstructor( 140 JsonValue.class, ClassLoader.class); 141 return constructor.newInstance(savedContext, classLoader); 142 } catch (final Exception e) { 143 throw new IllegalArgumentException( 144 "Unable to instantiate Context implementation class '" + className + "'", e); 145 } 146 } 147 148 @Override 149 public final String getContextName() { 150 return data.get(ATTR_NAME).asString(); 151 } 152 153 @Override 154 public final <T extends Context> T asContext(Class<T> clazz) { 155 Reject.ifNull(clazz, "clazz cannot be null"); 156 T context = asContext0(clazz); 157 if (context != null) { 158 return context; 159 } else { 160 throw new IllegalArgumentException("No context of type " + clazz.getName() + " found."); 161 } 162 } 163 164 @Override 165 public final Context getContext(String contextName) { 166 Context context = getContext0(contextName); 167 if (context != null) { 168 return context; 169 } else { 170 throw new IllegalArgumentException("No context of named " + contextName + " found."); 171 } 172 } 173 174 @Override 175 public final boolean containsContext(Class<? extends Context> clazz) { 176 return asContext0(clazz) != null; 177 } 178 179 @Override 180 public final boolean containsContext(String contextName) { 181 return getContext0(contextName) != null; 182 } 183 184 @Override 185 public final String getId() { 186 if (data.get(ATTR_ID).isNull() && !isRootContext()) { 187 return getParent().getId(); 188 } else { 189 return data.get(ATTR_ID).required().asString(); 190 } 191 } 192 193 @Override 194 public final Context getParent() { 195 return parent; 196 } 197 198 @Override 199 public final boolean isRootContext() { 200 return getParent() == null; 201 } 202 203 @Override 204 public JsonValue toJsonValue() { 205 final JsonValue value = data.copy(); 206 value.put(ATTR_PARENT, parent != null ? parent.toJsonValue().getObject() : null); 207 return value; 208 } 209 210 @Override 211 public String toString() { 212 return toJsonValue().toString(); 213 } 214 215 private <T extends Context> T asContext0(final Class<T> clazz) { 216 try { 217 for (Context context = this; context != null; context = context.getParent()) { 218 final Class<?> contextClass = context.getClass(); 219 if (clazz.isAssignableFrom(contextClass)) { 220 return contextClass.asSubclass(clazz).cast(context); 221 } 222 } 223 return null; 224 } catch (final Exception e) { 225 throw new IllegalArgumentException( 226 "Unable to instantiate Context implementation class '" + clazz.getName() + "'", e); 227 } 228 } 229 230 private Context getContext0(final String contextName) { 231 for (Context context = this; context != null; context = context.getParent()) { 232 if (context.getContextName().equals(contextName)) { 233 return context; 234 } 235 } 236 return null; 237 } 238 239}