diff --git a/src/main/java/org/springframework/hateoas/Link.java b/src/main/java/org/springframework/hateoas/Link.java index 4a3273c1..5183bb64 100755 --- a/src/main/java/org/springframework/hateoas/Link.java +++ b/src/main/java/org/springframework/hateoas/Link.java @@ -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 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 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 affordances) { + String deprecation, String profile, String name, @Nullable UriTemplate template, List affordances) { this.rel = rel; this.href = href; @@ -320,7 +333,7 @@ public class Link implements Serializable { */ @JsonIgnore public List getVariableNames() { - return template.getVariableNames(); + return template == null ? Collections.emptyList() : template.getVariableNames(); } /** @@ -330,7 +343,7 @@ public class Link implements Serializable { */ @JsonIgnore public List 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 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; + } } diff --git a/src/main/java/org/springframework/hateoas/UriTemplate.java b/src/main/java/org/springframework/hateoas/UriTemplate.java index f4d8acf2..f9daae6d 100644 --- a/src/main/java/org/springframework/hateoas/UriTemplate.java +++ b/src/main/java/org/springframework/hateoas/UriTemplate.java @@ -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, Serializable { private static final Pattern VARIABLE_REGEX = Pattern.compile("\\{([\\?\\&#/]?)([\\w\\,*]+)\\}"); private static final long serialVersionUID = -1007874653930162262L; - private static final ConcurrentLruCache 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, 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, 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, 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. - *

- * 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 the type of the key used for caching - * @param the type of the cached values - */ - private static class ConcurrentLruCache { - - private final int maxSize; - - private final ConcurrentLinkedDeque queue = new ConcurrentLinkedDeque<>(); - - private final ConcurrentHashMap cache = new ConcurrentHashMap<>(); - - private final ReadWriteLock lock; - - private final Function generator; - - private volatile int size = 0; - - public ConcurrentLruCache(int maxSize, Function 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(); - } - } - } }