2 * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
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
8 package org.opendaylight.genius.mdsalutil.cache;
10 import static java.util.Objects.requireNonNull;
12 import com.google.common.cache.CacheBuilder;
13 import com.google.common.cache.CacheLoader;
14 import com.google.common.cache.LoadingCache;
15 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
16 import java.util.Collection;
17 import java.util.Objects;
18 import java.util.Optional;
19 import java.util.concurrent.ExecutionException;
20 import java.util.concurrent.atomic.AtomicBoolean;
21 import java.util.function.BiFunction;
22 import java.util.function.Function;
23 import java.util.stream.Collectors;
24 import java.util.stream.Stream;
25 import javax.annotation.PreDestroy;
26 import org.eclipse.jdt.annotation.NonNull;
27 import org.opendaylight.genius.datastoreutils.SingleTransactionDataBroker;
28 import org.opendaylight.infrautils.caches.CacheProvider;
29 import org.opendaylight.mdsal.binding.api.ClusteredDataTreeChangeListener;
30 import org.opendaylight.mdsal.binding.api.DataBroker;
31 import org.opendaylight.mdsal.binding.api.DataObjectModification;
32 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
33 import org.opendaylight.mdsal.binding.api.DataTreeModification;
34 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
35 import org.opendaylight.mdsal.common.api.ReadFailedException;
36 import org.opendaylight.yangtools.concepts.ListenerRegistration;
37 import org.opendaylight.yangtools.yang.binding.DataObject;
38 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
43 * Caches DataObjects of a particular type. The cache is updated by a DataTreeChangeListener.
45 * @author Thomas Pantelis
47 public class DataObjectCache<K, V extends DataObject> implements AutoCloseable {
49 private static final Logger LOG = LoggerFactory.getLogger(DataObjectCache.class);
51 private final SingleTransactionDataBroker broker;
52 private final LoadingCache<K, Optional<V>> cache;
53 private final AtomicBoolean isClosed = new AtomicBoolean();
54 protected ListenerRegistration<?> listenerRegistration;
55 protected ClusteredDataTreeChangeListener<V> dataObjectListener;
60 * @param dataObjectClass the DataObject class to cache
61 * @param dataBroker the DataBroker
62 * @param datastoreType the LogicalDatastoreType
63 * @param cacheProvider the CacheProvider used to instantiate the Cache
64 * @param keyFunction the function used to convert or extract the key instance on change notification
65 * @param instanceIdFunction the function used to convert a key instance to an InstanceIdentifier on read
67 public DataObjectCache(Class<V> dataObjectClass, DataBroker dataBroker, LogicalDatastoreType datastoreType,
68 InstanceIdentifier<V> listetenerRegistrationPath, CacheProvider cacheProvider,
69 BiFunction<InstanceIdentifier<V>, V, K> keyFunction,
70 Function<K, InstanceIdentifier<V>> instanceIdFunction) {
71 this(dataObjectClass, dataBroker, datastoreType, cacheProvider, keyFunction, instanceIdFunction);
72 listenerRegistration = dataBroker.registerDataTreeChangeListener(DataTreeIdentifier.create(
73 datastoreType, listetenerRegistrationPath), dataObjectListener);
77 public DataObjectCache(Class<V> dataObjectClass, DataBroker dataBroker, LogicalDatastoreType datastoreType,
78 CacheProvider cacheProvider, BiFunction<InstanceIdentifier<V>, V, K> keyFunction,
79 Function<K, InstanceIdentifier<V>> instanceIdFunction) {
80 Objects.requireNonNull(keyFunction);
81 Objects.requireNonNull(instanceIdFunction);
82 this.broker = new SingleTransactionDataBroker(Objects.requireNonNull(dataBroker));
84 requireNonNull(cacheProvider, "cacheProvider");
85 cache = CacheBuilder.newBuilder().build(new CacheLoader<K, Optional<V>>() {
87 public Optional<V> load(K key) throws ReadFailedException, ExecutionException, InterruptedException {
88 return broker.syncReadOptional(datastoreType, instanceIdFunction.apply(key));
92 dataObjectListener = changes -> {
93 for (DataTreeModification<V> dataTreeModification : changes) {
94 DataObjectModification<V> rootNode = dataTreeModification.getRootNode();
95 InstanceIdentifier<V> path = dataTreeModification.getRootPath().getRootIdentifier();
96 switch (rootNode.getModificationType()) {
98 case SUBTREE_MODIFIED:
99 V dataAfter = rootNode.getDataAfter();
100 cache.put(keyFunction.apply(path, dataAfter), Optional.ofNullable(dataAfter));
101 added(path, dataAfter);
104 V dataBefore = rootNode.getDataBefore();
105 cache.invalidate(keyFunction.apply(path, dataBefore));
106 removed(path, dataBefore);
117 public void close() {
118 if (isClosed.compareAndSet(false, true)) {
119 if (listenerRegistration != null) {
120 listenerRegistration.close();
124 LOG.warn("Lifecycled object already closed; ignoring extra close()");
128 protected void checkIsClosed() throws ReadFailedException {
129 if (isClosed.get()) {
130 throw new ReadFailedException("Lifecycled object is already closed: " + this.toString());
135 * Gets the DataObject for the given key. If there's no DataObject cached, it will be read from the data store
136 * and put in the cache if it exists.
138 * @param key identifies the DataObject to query
139 * @return if the data for the supplied key exists, returns an Optional object containing the data; otherwise,
140 * returns Optional#absent()
141 * @throws ReadFailedException if that data isn't cached and the read to fetch it fails
144 // The ExecutionException cause should be a ReadFailedException - ok to cast.
145 @SuppressFBWarnings("BC_UNCONFIRMED_CAST_OF_RETURN_VALUE")
146 @SuppressWarnings("checkstyle:AvoidHidingCauseException")
147 public Optional<V> get(@NonNull K key) throws ReadFailedException {
150 return cache.get(key);
151 } catch (ExecutionException e) {
152 throw (ReadFailedException) e.getCause();
157 * Gets all DataObjects currently in the cache.
159 * @return the DataObjects currently in the cache
162 public Collection<V> getAllPresent() {
163 return cache.asMap().values().stream().flatMap(optional -> optional.isPresent()
164 ? Stream.of(optional.get()) : Stream.empty()).collect(Collectors.toList());
167 protected void added(InstanceIdentifier<V> path, V dataObject) {
170 protected void removed(InstanceIdentifier<V> path, V dataObject) {