BUG-7222: Fix clustering BGPPeer NPE
[bgpcep.git] / bgp / rib-impl / src / main / java / org / opendaylight / protocol / bgp / rib / impl / CachingImportPolicy.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.protocol.bgp.rib.impl;
9
10 import com.google.common.base.Preconditions;
11 import com.google.common.collect.Interner;
12 import com.google.common.collect.Interners;
13 import com.google.common.collect.MapMaker;
14
15 import java.util.IdentityHashMap;
16 import java.util.concurrent.ConcurrentMap;
17
18 import javax.annotation.Nonnull;
19 import javax.annotation.Nullable;
20 import javax.annotation.concurrent.NotThreadSafe;
21 import org.opendaylight.protocol.bgp.rib.impl.spi.AbstractImportPolicy;
22 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.tables.Attributes;
23 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
24 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
25
26 /**
27  * A caching decorator for {@link AbstractImportPolicy}. Performs caching of effective
28  * attributes using an {@link IdentityHashMap} for fast lookup and reuse of resulting
29  * objects.
30  */
31 @NotThreadSafe
32 final class CachingImportPolicy extends AbstractImportPolicy {
33
34     // A dummy ContainerNode, stored in the cache to indicate null effective attributes
35     private static final ContainerNode MASKED_NULL = ImmutableNodes.containerNode(Attributes.QNAME);
36
37     // We maintain a weak cache of returned effective attributes, so we end up reusing
38     // the same instance when asked. We set concurrency level to 1, as we do not expect
39     // the cache to be accessed from multiple threads. That may need to be changed
40     // if we end up sharing the cache across peers.
41     private final ConcurrentMap<ContainerNode, ContainerNode> cache =
42             new MapMaker().concurrencyLevel(1).weakKeys().weakValues().makeMap();
43
44     /*
45      * The cache itself is weak, which means we end up with identity hash/comparisons.
46      * That is good, but we want the cache to be effective even when equivalent attributes
47      * are presented. For that purpose we maintain a weak interner, which will allow us
48      * to map attributes to a canonical object without preventing garbage collection.
49      */
50     private final Interner<ContainerNode> interner = Interners.newWeakInterner();
51
52     private final AbstractImportPolicy delegate;
53
54     CachingImportPolicy(final AbstractImportPolicy delegate) {
55         this.delegate = Preconditions.checkNotNull(delegate);
56     }
57
58     @Nonnull private static ContainerNode maskNull(@Nullable final ContainerNode unmasked) {
59         return unmasked == null ? MASKED_NULL : unmasked;
60     }
61
62     @Nullable private static ContainerNode unmaskNull(@Nonnull final ContainerNode masked) {
63         return MASKED_NULL.equals(masked) ? null : masked;
64     }
65
66     @Override
67     public ContainerNode effectiveAttributes(final ContainerNode attributes) {
68         ContainerNode ret = this.cache.get(attributes);
69         if (ret != null) {
70             return unmaskNull(ret);
71         }
72
73         /*
74          * The cache returned empty. The reason for that may be that the attributes
75          * passed in are not identical to the ones forming the cache's key. Intern
76          * the passed attributes, which will result in a canonical reference.
77          *
78          * If the returned reference is different, attempt to look up in the cache
79          * again. If the reference is the same, we have just populated the interner
80          * and thus are on the path to create a new cache entry.
81          */
82         final ContainerNode interned = interner.intern(attributes);
83         if (interned != attributes) {
84             final ContainerNode retry = cache.get(interned);
85             if (retry != null) {
86                 return unmaskNull(retry);
87             }
88         }
89
90         final ContainerNode effective = delegate.effectiveAttributes(interned);
91
92         /*
93          * Populate the cache. Note that this may have raced with another thread,
94          * in which case we want to reuse the previous entry without replacing it.
95          * Check the result of conditional put and return it unmasked if it happens
96          * to be non-null. That will throw away the attributes we just created,
97          * but that's fine, as they have not leaked to heap yet and will be GC'd
98          * quickly.
99          */
100         final ContainerNode existing = cache.putIfAbsent(interned, maskNull(effective));
101         return existing != null ? unmaskNull(existing) : effective;
102
103     }
104 }