SoftCache.java
/*
* Copyright (c) 2007-2024 MetaSolutions AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.entrystore.impl;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.entrystore.Entry;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.net.URI;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
//TODO prioritize recently used objects so they are not
// garbage collected first. Simple use is to have hard references
// to everything used within the last 30 minutes, but that does
// take into account amount of available memory.
public class SoftCache {
@Getter
private final HashMap<URI, SoftReference<Entry>> cache = new HashMap<>();
HashMap<URI, Object> uri2entryURIs = new HashMap<>();
Thread remover;
ReferenceQueue<Entry> clearedRefs;
Log log = LogFactory.getLog(SoftCache.class);
@Getter
@Setter
private boolean shutdown = false;
public SoftCache() {
clearedRefs = new ReferenceQueue<>();
// start thread to delete cleared references from the cache
remover = new Remover(clearedRefs, this);
remover.start();
// add a shutdown hook to interrupt the endless loop
// the hook is only called when the whole VM is shutdown
Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));
}
public void clear() {
synchronized (cache) {
cache.clear();
uri2entryURIs.clear();
}
}
public void put(Entry entry) {
synchronized (cache) {
URI entryURI = entry.getEntryURI();
cache.put(entryURI, new SoftReference<>(entry, clearedRefs));
push(entry.getLocalMetadataURI(), entryURI);
push(entry.getExternalMetadataURI(), entryURI);
push(entry.getResourceURI(), entryURI);
push(entry.getRelationURI(), entryURI);
}
}
private void push(URI from, URI to) {
if (from == null || to == null) {
return;
}
Object existingTo = uri2entryURIs.get(from);
if (existingTo == null) {
uri2entryURIs.put(from, to);
} else {
if (existingTo instanceof Set) {
((Set<URI>) existingTo).add(to);
} else {
HashSet<URI> set = new HashSet<>();
set.add((URI) existingTo);
set.add(to);
uri2entryURIs.put(from, set);
}
}
}
private void pop(URI from, URI to) {
if (from == null || to == null) {
return;
}
Object existingTo = uri2entryURIs.get(from);
if (existingTo != null) {
if (existingTo instanceof Set) {
((Set) existingTo).remove(to);
if (((Set) existingTo).isEmpty()) {
uri2entryURIs.remove(from);
}
} else if (existingTo.equals(to)){
uri2entryURIs.remove(from);
}
}
}
public void remove(Entry entry) {
if(entry == null) return;
synchronized (cache) {
URI entryURI = entry.getEntryURI();
cache.remove(entryURI);
pop(entry.getLocalMetadataURI(), entryURI);
pop(entry.getExternalMetadataURI(), entryURI);
pop(entry.getResourceURI(), entryURI);
pop(entry.getRelationURI(), entryURI);
}
}
public Entry getByEntryURI(URI uri) {
SoftReference<Entry> sr = cache.get(uri);
if (sr != null) {
return sr.get();
}
return null;
}
public Set<Entry> getByURI(URI uri) {
if (uri2entryURIs.containsKey(uri)) {
HashSet<Entry> entries = new HashSet<>();
Object entryUris = uri2entryURIs.get(uri);
if (entryUris instanceof Set) {
for (URI entryURI : ((Set<URI>) entryUris)) {
entries.add(getByEntryURI(entryURI));
}
} else {
entries.add(getByEntryURI((URI) entryUris));
}
return entries;
}
return null;
}
public void shutdown() {
if (remover == null || (shutdown && remover.isInterrupted())) {
return;
}
log.info("Shutting down SoftCache");
setShutdown(true);
remover.interrupt();
}
private class Remover extends Thread {
ReferenceQueue<Entry> refQ;
SoftCache cache;
public Remover(ReferenceQueue<Entry> rq, SoftCache cache) {
super();
this.refQ = rq;
this.cache = cache;
setDaemon(true);
}
public void run() {
try {
while (!shutdown) {
Reference ref = refQ.remove();
cache.remove((Entry) ref.get());
}
} catch (InterruptedException e) {
log.info("SoftCache remover got interrupted, shutting down");
}
}
}
}