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