#1287 - Remove cache from UriTemplate in favor of better handling in Link.

We now avoid creating the UriTemplate instance for a Link if the Link does not contain curly braces in the first place. That allows us to remove the cache within UriTemplate in the first place.
This commit is contained in:
Oliver Drotbohm
2020-07-23 22:50:15 +02:00
parent 65a692e33d
commit 348d6e4514
2 changed files with 33 additions and 99 deletions

View File

@@ -26,6 +26,7 @@ import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -88,7 +89,7 @@ public class Link implements Serializable {
private String deprecation;
private String profile;
private String name;
private @JsonIgnore UriTemplate template;
private @JsonIgnore @Nullable UriTemplate template;
private @JsonIgnore List<Affordance> affordances;
/**
@@ -112,7 +113,7 @@ public class Link implements Serializable {
*/
@Deprecated
public Link(String href, String rel) {
this(UriTemplate.of(href), LinkRelation.of(rel));
this(href, LinkRelation.of(rel));
}
/**
@@ -124,7 +125,7 @@ public class Link implements Serializable {
*/
@Deprecated
public Link(String href, LinkRelation rel) {
this(UriTemplate.of(href), rel);
this(href, templateOrNull(href), rel, Collections.emptyList());
}
/**
@@ -169,8 +170,20 @@ public class Link implements Serializable {
this.affordances = affordances;
}
private Link(String href, @Nullable UriTemplate template, LinkRelation rel, List<Affordance> affordances) {
Assert.hasText(href, "Href must not be null or empty!");
Assert.notNull(rel, "LinkRelation must not be null!");
Assert.notNull(affordances, "Affordances must not be null!");
this.href = href;
this.template = template;
this.rel = rel;
this.affordances = affordances;
}
private Link(LinkRelation rel, String href, String hreflang, String media, String title, String type,
String deprecation, String profile, String name, UriTemplate template, List<Affordance> affordances) {
String deprecation, String profile, String name, @Nullable UriTemplate template, List<Affordance> affordances) {
this.rel = rel;
this.href = href;
@@ -320,7 +333,7 @@ public class Link implements Serializable {
*/
@JsonIgnore
public List<String> getVariableNames() {
return template.getVariableNames();
return template == null ? Collections.emptyList() : template.getVariableNames();
}
/**
@@ -330,7 +343,7 @@ public class Link implements Serializable {
*/
@JsonIgnore
public List<TemplateVariable> getVariables() {
return template.getVariables();
return template == null ? Collections.emptyList() : template.getVariables();
}
/**
@@ -339,7 +352,7 @@ public class Link implements Serializable {
* @return
*/
public boolean isTemplated() {
return !template.getVariables().isEmpty();
return template == null ? false : !template.getVariables().isEmpty();
}
/**
@@ -349,7 +362,7 @@ public class Link implements Serializable {
* @return
*/
public Link expand(Object... arguments) {
return of(template.expand(arguments).toString(), getRel());
return template == null ? this : of(template.expand(arguments).toString(), getRel());
}
/**
@@ -359,7 +372,7 @@ public class Link implements Serializable {
* @return
*/
public Link expand(Map<String, ?> arguments) {
return of(template.expand(arguments).toString(), getRel());
return template == null ? this : of(template.expand(arguments).toString(), getRel());
}
/**
@@ -663,7 +676,7 @@ public class Link implements Serializable {
@JsonProperty
public UriTemplate getTemplate() {
return this.template;
return template == null ? UriTemplate.of(href) : this.template;
}
@Override
@@ -727,4 +740,12 @@ public class Link implements Serializable {
return linkString;
}
@Nullable
private static UriTemplate templateOrNull(String href) {
Assert.notNull(href, "Href must not be null!");
return href.contains("{") ? UriTemplate.of(href) : null;
}
}

View File

@@ -25,11 +25,6 @@ import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -59,8 +54,6 @@ public class UriTemplate implements Iterable<TemplateVariable>, Serializable {
private static final Pattern VARIABLE_REGEX = Pattern.compile("\\{([\\?\\&#/]?)([\\w\\,*]+)\\}");
private static final long serialVersionUID = -1007874653930162262L;
private static final ConcurrentLruCache<String, UriTemplate> CACHE = new ConcurrentLruCache<>(1024, UriTemplate::new);
private final TemplateVariables variables;
private String baseUri;
private transient UriBuilderFactory factory;
@@ -138,7 +131,7 @@ public class UriTemplate implements Iterable<TemplateVariable>, Serializable {
Assert.hasText(template, "Template must not be null or empty!");
return CACHE.get(template);
return new UriTemplate(template);
}
/**
@@ -151,7 +144,7 @@ public class UriTemplate implements Iterable<TemplateVariable>, Serializable {
Assert.hasText(template, "Template must not be null or empty!");
return CACHE.get(template).with(variables);
return new UriTemplate(template).with(variables);
}
/**
@@ -430,84 +423,4 @@ public class UriTemplate implements Iterable<TemplateVariable>, Serializable {
this.factory = createFactory(baseUri);
}
// Copy of ConcurrentLruCache of Spring Framework's MimeTypeUtils
/**
* Simple Least Recently Used cache, bounded by the maximum size given to the class constructor.
* <p>
* This implementation is backed by a {@code ConcurrentHashMap} for storing the cached values and a
* {@code ConcurrentLinkedQueue} for ordering the keys and choosing the least recently used key when the cache is at
* full capacity.
*
* @param <K> the type of the key used for caching
* @param <V> the type of the cached values
*/
private static class ConcurrentLruCache<K, V> {
private final int maxSize;
private final ConcurrentLinkedDeque<K> queue = new ConcurrentLinkedDeque<>();
private final ConcurrentHashMap<K, V> cache = new ConcurrentHashMap<>();
private final ReadWriteLock lock;
private final Function<K, V> generator;
private volatile int size = 0;
public ConcurrentLruCache(int maxSize, Function<K, V> generator) {
Assert.isTrue(maxSize > 0, "LRU max size should be positive");
Assert.notNull(generator, "Generator function should not be null");
this.maxSize = maxSize;
this.generator = generator;
this.lock = new ReentrantReadWriteLock();
}
public V get(K key) {
V cached = this.cache.get(key);
if (cached != null) {
if (this.size < this.maxSize) {
return cached;
}
this.lock.readLock().lock();
try {
if (this.queue.removeLastOccurrence(key)) {
this.queue.offer(key);
}
return cached;
} finally {
this.lock.readLock().unlock();
}
}
this.lock.writeLock().lock();
try {
// Retrying in case of concurrent reads on the same key
cached = this.cache.get(key);
if (cached != null) {
if (this.queue.removeLastOccurrence(key)) {
this.queue.offer(key);
}
return cached;
}
// Generate value first, to prevent size inconsistency
V value = this.generator.apply(key);
int cacheSize = this.size;
if (cacheSize == this.maxSize) {
K leastUsed = this.queue.poll();
if (leastUsed != null) {
this.cache.remove(leastUsed);
cacheSize--;
}
}
this.queue.offer(key);
this.cache.put(key, value);
this.size = cacheSize + 1;
return value;
} finally {
this.lock.writeLock().unlock();
}
}
}
}