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-2014 ForgeRock AS. 016 */ 017 018package org.forgerock.openig.heap; 019 020// TODO: consider detecting cyclic dependencies 021 022import static java.lang.String.*; 023import static java.util.Arrays.*; 024import static java.util.Collections.*; 025import static org.forgerock.json.fluent.JsonValue.*; 026import static org.forgerock.openig.decoration.global.GlobalDecorator.*; 027import static org.forgerock.openig.log.LogSink.*; 028import static org.forgerock.openig.util.Json.*; 029import static org.forgerock.util.Reject.*; 030 031import java.util.ArrayDeque; 032import java.util.ArrayList; 033import java.util.Arrays; 034import java.util.Deque; 035import java.util.HashMap; 036import java.util.LinkedHashMap; 037import java.util.List; 038import java.util.Map; 039 040import org.forgerock.json.fluent.JsonValue; 041import org.forgerock.json.fluent.JsonValueException; 042import org.forgerock.openig.decoration.Context; 043import org.forgerock.openig.decoration.Decorator; 044import org.forgerock.openig.decoration.global.GlobalDecorator; 045import org.forgerock.openig.handler.Handler; 046import org.forgerock.openig.log.LogSink; 047import org.forgerock.openig.log.Logger; 048import org.forgerock.openig.util.MultiValueMap; 049 050/** 051 * The concrete implementation of a heap. Provides methods to initialize and destroy a heap. 052 * A Heap can be part of a heap hierarchy: if the queried object is not found locally, and if it has a parent, 053 * the parent will be queried (and this, recursively until there is no parent anymore). 054 */ 055public class HeapImpl implements Heap { 056 057 /** 058 * List of attributes that should be ignored when looking for decorator's configuration. 059 */ 060 private static final List<String> EXCLUDED_ATTRIBUTES = asList("type", "name", "config"); 061 062 /** 063 * Parent heap to delegate queries to if nothing is found in the local heap. 064 * It may be null if this is the root heap (built by the system). 065 */ 066 private final HeapImpl parent; 067 068 /** 069 * Heap name. 070 */ 071 private final Name name; 072 073 /** Heaplets mapped to heaplet identifiers in the heap configuration. */ 074 private Map<String, Heaplet> heaplets = new HashMap<String, Heaplet>(); 075 076 /** Configuration objects for heaplets. */ 077 private Map<String, JsonValue> configs = new HashMap<String, JsonValue>(); 078 079 /** Objects allocated in the heap mapped to heaplet names. */ 080 private Map<String, Object> objects = new HashMap<String, Object>(); 081 082 /** Per-heaplet decoration contexts mapped to heaplet names. */ 083 private Map<String, Context> contexts = new HashMap<String, Context>(); 084 085 /** Per-heaplet decoration(s) mapped to heaplet names. */ 086 private MultiValueMap<String, JsonValue> decorations = 087 new MultiValueMap<String, JsonValue>(new LinkedHashMap<String, List<JsonValue>>()); 088 089 /** 090 * Decorator for the 'main handler' reference. 091 */ 092 private Decorator topLevelHandlerDecorator; 093 094 /** 095 * Top-level 'handler' reference decorations (effectively the root node of configuration). 096 */ 097 private JsonValue config; 098 099 /** 100 * Heap logger. 101 */ 102 private Logger logger; 103 104 /** 105 * Keep track of objects being resolved, used to avoid recursive issues. 106 */ 107 private Deque<String> resolving = new ArrayDeque<String>(); 108 109 /** 110 * List of default object declarations to be inserted in this heap if no user-provided objects were found. 111 */ 112 private List<JsonValue> defaults = new ArrayList<JsonValue>(); 113 114 /** 115 * Builds an anonymous root heap (will be referenced by children but has no parent itself). 116 * Intended for tests only. 117 */ 118 HeapImpl() { 119 this((HeapImpl) null); 120 } 121 122 /** 123 * Builds a new anonymous heap that is a child of the given heap. 124 * Intended for tests only. 125 * 126 * @param parent 127 * parent heap. 128 */ 129 HeapImpl(final HeapImpl parent) { 130 this(parent, Name.of("anonymous")); 131 } 132 133 /** 134 * Builds a root heap (will be referenced by children but has no parent itself). 135 * 136 * @param name 137 * local name of this heap 138 */ 139 public HeapImpl(final Name name) { 140 this(null, name); 141 } 142 143 /** 144 * Builds a new heap that is a child of the given heap. 145 * 146 * @param parent 147 * parent heap. 148 * @param name 149 * local name of this heap 150 */ 151 public HeapImpl(final HeapImpl parent, final Name name) { 152 this.parent = parent; 153 this.name = checkNotNull(name); 154 } 155 156 /** 157 * Initializes the heap using the given configuration. Once complete, all heaplets will 158 * be loaded and all associated objects are allocated using each heaplet instance's 159 * configuration. 160 * 161 * @param config the configuration root. 162 * @param reservedFieldNames the names of reserved top level fields in the config which 163 * should not be parsed as global decorators. 164 * @throws HeapException if an exception occurs allocating heaplets. 165 * @throws JsonValueException if the configuration object is malformed. 166 */ 167 public synchronized void init(JsonValue config, String... reservedFieldNames) 168 throws HeapException { 169 // process configuration object model structure 170 this.config = config; 171 boolean logDeprecationWarning = false; 172 JsonValue heap = config.get("heap").defaultTo(emptyList()); 173 if (heap.isMap()) { 174 /* 175 * In OpenIG < 3.1 the heap objects were listed in a child "objects" 176 * array. The extra nesting was found to be redundant and removed in 177 * 3.1. We continue to allow it in order to maintain backwards 178 * compatibility. 179 */ 180 heap = heap.get("objects").required(); 181 182 // We cannot log anything just yet because the heap is not initialized. 183 logDeprecationWarning = true; 184 } 185 for (JsonValue object : heap.expect(List.class)) { 186 addDeclaration(object); 187 } 188 189 // Register the default objects if the user has not overridden them 190 // Notice this will only verify objects declared in the heap, not the inline declarations 191 for (JsonValue value : defaults) { 192 String name = value.get("name").required().asString(); 193 if (heaplets.get(name) == null) { 194 addDeclaration(value); 195 } 196 // otherwise the object configuration has been overridden 197 } 198 199 // register global decorators, ensuring that reserved field names are filtered out 200 int sz = reservedFieldNames.length; 201 String[] allReservedFieldNames = Arrays.copyOf(reservedFieldNames, sz + 1); 202 allReservedFieldNames[sz] = "heap"; 203 topLevelHandlerDecorator = new GlobalDecorator(null, config, allReservedFieldNames); 204 205 if (config.isDefined("globalDecorators")) { 206 Decorator parentGlobalDecorator = 207 parent != null ? parent.get(GLOBAL_DECORATOR_HEAP_KEY, Decorator.class) : null; 208 put(GLOBAL_DECORATOR_HEAP_KEY, 209 new GlobalDecorator(parentGlobalDecorator, config.get("globalDecorators").expect(Map.class))); 210 } 211 212 // instantiate all objects, recursively allocating dependencies 213 for (String name : new ArrayList<String>(heaplets.keySet())) { 214 get(name, Object.class); 215 } 216 217 // We can log a warning now that the heap is initialized. 218 logger = new Logger(resolve(config.get("logSink").defaultTo(LOGSINK_HEAP_KEY), 219 LogSink.class, 220 true), name); 221 if (logDeprecationWarning) { 222 logger.warning("The configuration field heap/objects has been deprecated. Heap objects " 223 + "should now be listed directly in the top level \"heap\" field, " 224 + "e.g. { \"heap\" : [ objects... ] }."); 225 } 226 } 227 228 /** 229 * Add a default JsonValue object declaration in this heap. 230 * 231 * This method should only be called prior to {@link #init(JsonValue, String...)}: values provided after init() has 232 * been called will be ignored. 233 * 234 * Notice that if the caller add 2 declarations with the same name, that will ends up with an exception thrown 235 * when heap will be initialized. 236 * 237 * @param object 238 * Object declaration (has to contains a {@literal name} attribute) 239 */ 240 public void addDefaultDeclaration(final JsonValue object) { 241 defaults.add(object); 242 } 243 244 /** 245 * Add the given JsonValue as a new object declaration in this heap. The given object must be a valid object 246 * declaration ({@literal name}, {@literal type} and {@literal config} attributes). If not, a JsonValueException 247 * will be thrown. After this method is called, a new object is available in the heap. 248 * 249 * <p>The {@literal config} attribute is optional and accordingly, if empty or null, its declaration can be omitted. 250 * 251 * @param object 252 * object declaration to add to the heap. 253 */ 254 private void addDeclaration(final JsonValue object) { 255 object.required().expect(Map.class); 256 Heaplet heaplet = Heaplets.getHeaplet(asClass(object.get("type").required())); 257 if (heaplet == null) { 258 throw new JsonValueException(object.get("type"), "no heaplet available to initialize object"); 259 } 260 // objects[n].name (string) 261 String name = object.get("name").required().asString(); 262 if (heaplets.get(name) != null) { 263 throw new JsonValueException(object.get("name"), "object already defined"); 264 } 265 // remove pre-allocated objects to be replaced 266 objects.remove(name); 267 heaplets.put(name, heaplet); 268 // objects[n].config (object) 269 configs.put(name, object.get("config").defaultTo(emptyMap()).expect(Map.class)); 270 // Store decorations 271 for (JsonValue candidate : object) { 272 // Exclude standard declaration elements 273 if (!EXCLUDED_ATTRIBUTES.contains(candidate.getPointer().leaf())) { 274 decorations.add(name, candidate); 275 } 276 } 277 } 278 279 @Override 280 public <T> T get(final String name, final Class<T> type) throws HeapException { 281 ExtractedObject extracted = extract(name); 282 if (extracted.object == null) { 283 return null; 284 } 285 return type.cast(applyGlobalDecorations(extracted)); 286 } 287 288 /** 289 * Extract the given named heap object from this heap or its parent (if any). 290 * The returned pointer is never {@code null} but can contains {@code null} {@literal object} and {@literal 291 * context}. 292 * The {@literal object} reference has only be decorated with it's locally (per-heaplet) defined decorations. 293 * This is the responsibility of the initial 'requester' heap (the one that have its {@link #get(String, Class)} 294 * method called in the first place) to complete the decoration with global decorators. 295 * 296 * @param name heap object name 297 * @return an {@link ExtractedObject} pair-like structure that may contains both the heap object and its 298 * associated context (never returns {@code null}) 299 * @throws HeapException if extraction failed 300 */ 301 ExtractedObject extract(final String name) throws HeapException { 302 if (resolving.contains(name)) { 303 // Fail for recursive object resolution 304 throw new HeapException( 305 format("Object '%s' is already being constructed (probably a duplicate name in configuration)", 306 name)); 307 } 308 Object object = objects.get(name); 309 if (object == null) { 310 Heaplet heaplet = heaplets.get(name); 311 if (heaplet != null) { 312 try { 313 resolving.push(name); 314 JsonValue configuration = configs.get(name); 315 object = heaplet.create(this.name.child(name), configuration, this); 316 if (object == null) { 317 throw new HeapException(new NullPointerException()); 318 } 319 object = applyObjectLevelDecorations(name, object, configuration); 320 put(name, object); 321 } finally { 322 resolving.pop(); 323 } 324 } else if (parent != null) { 325 // no heaplet available, query parent (if any) 326 return parent.extract(name); 327 } 328 } 329 return new ExtractedObject(object, contexts.get(name)); 330 } 331 332 @Override 333 public <T> T resolve(final JsonValue reference, final Class<T> type) throws HeapException { 334 return resolve(reference, type, false); 335 } 336 337 @Override 338 public <T> T resolve(final JsonValue reference, final Class<T> type, final boolean optional) throws HeapException { 339 340 // If optional if set, accept that the provided reference may wrap a null 341 if (optional && reference.isNull()) { 342 return null; 343 } 344 345 // Otherwise we require a value 346 JsonValue required = reference.required(); 347 if (required.isString()) { 348 // handle named reference 349 T value = get(required.asString(), type); 350 if (value == null) { 351 throw new JsonValueException(reference, "Object " + reference.asString() + " not found in heap"); 352 } 353 return value; 354 } else if (required.isMap()) { 355 // handle inline declaration 356 String generated = name(required); 357 358 // when resolve() is called multiple times with the same reference, this prevent "already registered" errors 359 T value = get(generated, type); 360 if (value == null) { 361 // First resolution 362 required.put("name", generated); 363 addDeclaration(required); 364 // Get decorated object 365 value = get(generated, type); 366 if (value == null) { 367 // Very unlikely to happen 368 throw new JsonValueException(reference, "Reference is not a valid heap object"); 369 } 370 } 371 return value; 372 } 373 throw new JsonValueException(reference, 374 format("JsonValue[%s] is neither a String nor a Map (inline declaration)", 375 reference.getPointer())); 376 } 377 378 /** 379 * Infer a locally unique name for the given object declaration. 380 * If a {@literal name} attribute is provided, simply return its value as name, otherwise compose a 381 * unique name composed of both the declaration JSON pointer (map to the location within the JSON file) and 382 * the value of the {@literal type} attribute (ease to identify the object). 383 * <p> 384 * The following declaration would return {@literal Inline}: 385 * <pre> 386 * {@code 387 * { 388 * "name": "Inline", 389 * "type": "Router" 390 * } 391 * } 392 * </pre> 393 * <p> 394 * And this one would return {@literal {WelcomeHandler}/heap/objects/0/config/defaultHandler}: 395 * <pre> 396 * {@code 397 * { 398 * "type": "WelcomeHandler" 399 * } 400 * } 401 * </pre> 402 * @param declaration source inline object declaration 403 * @return a locally unique name 404 */ 405 public static String name(final JsonValue declaration) { 406 JsonValue node = declaration.get("name"); 407 if (node.isNull()) { 408 String location = declaration.getPointer().toString(); 409 String type = declaration.get("type").required().asString(); 410 return format("{%s}%s", type, location); 411 } 412 return node.asString(); 413 } 414 415 /** 416 * Puts an object into the heap. If an object already exists in the heap with the 417 * specified name, it is overwritten. 418 * 419 * @param name name of the object to be put into the heap. 420 * @param object the object to be put into the heap. 421 */ 422 public synchronized void put(final String name, final Object object) { 423 objects.put(name, object); 424 contexts.put(name, new DecorationContext(this, this.name.child(name), json(emptyMap()))); 425 } 426 427 /** 428 * Decorates the given extracted heap object with global decorators. 429 * 430 * @return the extracted object (only decorated with its local decorations). 431 * @throws HeapException 432 * if a decorator failed to apply 433 */ 434 private Object applyGlobalDecorations(final ExtractedObject extracted) throws HeapException { 435 436 // Fast exit if there is nothing to decorate 437 if (extracted.object == null) { 438 return null; 439 } 440 441 // Starts with the original object 442 Object decorated = extracted.object; 443 444 // Avoid decorating decorators themselves 445 // Avoid StackOverFlow Exceptions because of infinite recursion 446 if (decorated instanceof Decorator) { 447 return decorated; 448 } 449 450 // Apply global decorations (may be inherited from parent heap) 451 ExtractedObject deco = extract(GLOBAL_DECORATOR_HEAP_KEY); 452 if (deco.object != null) { 453 Decorator globalDecorator = (Decorator) deco.object; 454 decorated = globalDecorator.decorate(decorated, null, extracted.context); 455 } 456 457 return decorated; 458 } 459 460 /** 461 * Decorates the given (local) heap object instance with its own decorations (decorations within the heap object 462 * declaration). 463 * 464 * @param name 465 * name of the heap object 466 * @param object 467 * delegate (to be decorated) instance 468 * @param configuration 469 * heap object configuration 470 * @return the decorated instance (may be null if delegate object is null) 471 * @throws HeapException 472 * when we cannot get a declared decorator from the heap 473 */ 474 private Object applyObjectLevelDecorations(final String name, 475 final Object object, 476 final JsonValue configuration) throws HeapException { 477 Object decorated = object; 478 479 // Avoid decorating decorators themselves 480 // Avoid StackOverFlow Exceptions because of infinite recursion 481 if (decorated instanceof Decorator) { 482 return decorated; 483 } 484 485 // Create a context object for holding shared values 486 Context context = new DecorationContext(this, 487 this.name.child(name), 488 configuration); 489 // .. and save it for later use in extract() 490 contexts.put(name, context); 491 492 if (decorations.containsKey(name)) { 493 // We have decorators for this instance, try to apply them 494 for (JsonValue decoration : decorations.get(name)) { 495 496 // The element name is the decorator heap object name 497 String decoratorName = decoration.getPointer().leaf(); 498 Decorator decorator = get(decoratorName, Decorator.class); 499 if (decorator != null) { 500 // We just ignore when no named decorator is found 501 // TODO Keep a list of intermediate objects for later use 502 decorated = decorator.decorate(decorated, decoration, context); 503 } 504 } 505 } 506 return decorated; 507 } 508 509 /** 510 * Destroys the objects on the heap and dereferences all associated objects. This method 511 * calls the heaplet {@code destroy} method for each object in the heap to provide a 512 * chance for system resources to be freed. 513 */ 514 public synchronized void destroy() { 515 // save the heaplets locally to send destroy notifications 516 Map<String, Heaplet> h = heaplets; 517 // prevent any further (inadvertent) object allocations 518 heaplets = new HashMap<String, Heaplet>(); 519 // all allocated objects are no longer in this heap 520 objects.clear(); 521 // iterate through saved heaplets, notifying about destruction 522 for (String name : h.keySet()) { 523 h.get(name).destroy(); 524 } 525 contexts.clear(); 526 527 } 528 529 /** 530 * Returns the {@link Handler} object referenced by the {@literal handler} top-level attribute. 531 * The returned object is fully decorated (including top-level reference decorations). 532 * 533 * @return the {@link Handler} object referenced by the {@literal handler} top-level attribute 534 * @throws HeapException if object resolution failed 535 */ 536 public Handler getHandler() throws HeapException { 537 JsonValue reference = getWithDeprecation(config, logger, "handler", "handlerObject"); 538 Handler handler = resolve(reference, Handler.class); 539 // FIXME: how to grab the decoration context of this object (it may not origin from this heap) ? 540 DecorationContext context = new DecorationContext(this, 541 name.child("top-level-handler"), 542 json(emptyMap())); 543 return (Handler) topLevelHandlerDecorator.decorate(handler, null, context); 544 } 545 546 /** 547 * A simple data holder object letting decorators known about the decorated heap object. 548 */ 549 private class DecorationContext implements Context { 550 private final Heap heap; 551 private final Name name; 552 private final JsonValue config; 553 554 public DecorationContext(final Heap heap, 555 final Name name, 556 final JsonValue config) { 557 this.heap = heap; 558 this.name = name; 559 this.config = config; 560 } 561 562 @Override 563 public Heap getHeap() { 564 return heap; 565 } 566 567 @Override 568 public Name getName() { 569 return name; 570 } 571 572 @Override 573 public JsonValue getConfig() { 574 return config; 575 } 576 } 577 578 /** 579 * Pair object containing both the retrieved heap object instance with its origin decoration context. 580 */ 581 private static class ExtractedObject { 582 Object object; 583 Context context; 584 585 /** 586 * Builds a new pair object. 587 * 588 * @param object object instance (may be null) 589 * @param context decoration context (may be null) 590 */ 591 public ExtractedObject(final Object object, final Context context) { 592 this.object = object; 593 this.context = context; 594 } 595 } 596}