Skip to content

Commit

Permalink
Add caching and improve performance
Browse files Browse the repository at this point in the history
Signed-off-by: jansupol <[email protected]>
  • Loading branch information
jansupol committed Dec 1, 2022
1 parent bb65db4 commit 6108093
Show file tree
Hide file tree
Showing 17 changed files with 1,202 additions and 181 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/

package org.glassfish.jersey.internal.util.collection;

import javax.ws.rs.core.MultivaluedMap;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
* The {@link MultivaluedMap} wrapper that is able to set guards observing changes of values represented by a key.
* @param <V> The value type of the wrapped {@code MultivaluedMap}.
*
* @since 2.38
*/
public class GuardianStringKeyMultivaluedMap<V> implements MultivaluedMap<String, V> {

private final MultivaluedMap<String, V> inner;
private final Map<String, Boolean> guards = new HashMap<>();

public GuardianStringKeyMultivaluedMap(MultivaluedMap<String, V> inner) {
this.inner = inner;
}

@Override
public void putSingle(String key, V value) {
observe(key);
inner.putSingle(key, value);
}

@Override
public void add(String key, V value) {
observe(key);
inner.add(key, value);
}

@Override
public V getFirst(String key) {
return inner.getFirst(key);
}

@Override
public void addAll(String key, V... newValues) {
observe(key);
inner.addAll(key, newValues);
}

@Override
public void addAll(String key, List<V> valueList) {
observe(key);
inner.addAll(key, valueList);
}

@Override
public void addFirst(String key, V value) {
observe(key);
inner.addFirst(key, value);
}

@Override
public boolean equalsIgnoreValueOrder(MultivaluedMap<String, V> otherMap) {
return inner.equalsIgnoreValueOrder(otherMap);
}

@Override
public int size() {
return inner.size();
}

@Override
public boolean isEmpty() {
return inner.isEmpty();
}

@Override
public boolean containsKey(Object key) {
return inner.containsKey(key);
}

@Override
public boolean containsValue(Object value) {
return inner.containsValue(value);
}

@Override
public List<V> get(Object key) {
return inner.get(key);
}

@Override
public List<V> put(String key, List<V> value) {
observe(key);
return inner.put(key, value);
}

@Override
public List<V> remove(Object key) {
if (key != null) {
observe(key.toString());
}
return inner.remove(key);
}

@Override
public void putAll(Map<? extends String, ? extends List<V>> m) {
for (String key : m.keySet()) {
observe(key);
}
inner.putAll(m);
}

@Override
public void clear() {
observeAll();
inner.clear();
}

@Override
public Set<String> keySet() {
return inner.keySet();
}

@Override
public Collection<List<V>> values() {
return inner.values();
}

@Override
public Set<Entry<String, List<V>>> entrySet() {
return inner.entrySet();
}

/**
* Observe changes of a value represented by the key.
* @param key the key values to observe
*/
public void setGuard(String key) {
guards.put(key, false);
}

/**
* Get all the guarded keys
* @return a {@link Set} of keys guarded.
*/
public Set<String> getGuards() {
return guards.keySet();
}

/**
* Return true when the value represented by the key has changed. Resets any observation - the operation is not idempotent.
* @param key the Key observed.
* @return whether the value represented by the key has changed.
*/
public boolean isObservedAndReset(String key) {
Boolean observed = guards.get(key);
guards.put(key, false);
return observed != null && observed;
}

private void observe(String key) {
for (Map.Entry<String, Boolean> guard : guards.entrySet()) {
if (guard.getKey().equals(key)) {
guard.setValue(true);
}
}
}

private void observeAll() {
for (Map.Entry<String, Boolean> guard : guards.entrySet()) {
guard.setValue(true);
}
}

@Override
public String toString() {
return inner.toString();
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GuardianStringKeyMultivaluedMap<?> that = (GuardianStringKeyMultivaluedMap<?>) o;
return inner.equals(that.inner) && guards.equals(that.guards);
}

@Override
public int hashCode() {
return Objects.hash(inner, guards);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/

package org.glassfish.jersey.internal.util.collection;

import org.glassfish.jersey.internal.guava.Cache;
import org.glassfish.jersey.internal.guava.CacheBuilder;

import java.util.concurrent.TimeUnit;

/**
* An abstract LRU interface wrapping an actual LRU implementation.
* @param <K> Key type
* @param <V> Value type
* @Since 2.38
*/
public abstract class LRU<K, V> {

/**
* Returns the value associated with {@code key} in this cache, or {@code null} if there is no
* cached value for {@code key}.
*/
public abstract V getIfPresent(Object key);

/**
* Associates {@code value} with {@code key} in this cache. If the cache previously contained a
* value associated with {@code key}, the old value is replaced by {@code value}.
*/
public abstract void put(K key, V value);

/**
* Create new LRU
* @return new LRU
*/
public static <K, V> LRU<K, V> create() {
return LRUFactory.createLRU();
}

private static class LRUFactory {
// TODO configure via the Configuration
public static final int LRU_CACHE_SIZE = 128;
public static final long TIMEOUT = 5000L;
private static <K, V> LRU<K, V> createLRU() {
final Cache<K, V> CACHE = CacheBuilder.newBuilder()
.maximumSize(LRU_CACHE_SIZE)
.expireAfterAccess(TIMEOUT, TimeUnit.MILLISECONDS)
.build();
return new LRU<K, V>() {
@Override
public V getIfPresent(Object key) {
return CACHE.getIfPresent(key);
}

@Override
public void put(K key, V value) {
CACHE.put(key, value);
}
};
}
}


}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -102,7 +102,7 @@ public static AbstractMultivaluedMap<String, Object> createOutbound() {
* if the supplied header value is {@code null}.
*/
@SuppressWarnings("unchecked")
private static String asString(final Object headerValue, RuntimeDelegate rd) {
public static String asString(final Object headerValue, RuntimeDelegate rd) {
if (headerValue == null) {
return null;
}
Expand Down Expand Up @@ -149,7 +149,7 @@ public static String asString(final Object headerValue, Configuration configurat
* will be called for before element conversion.
* @return String view of header values.
*/
private static List<String> asStringList(final List<Object> headerValues, final RuntimeDelegate rd) {
public static List<String> asStringList(final List<Object> headerValues, final RuntimeDelegate rd) {
if (headerValues == null || headerValues.isEmpty()) {
return Collections.emptyList();
}
Expand Down Expand Up @@ -191,7 +191,24 @@ public static MultivaluedMap<String, String> asStringHeaders(
return null;
}

final RuntimeDelegate rd = RuntimeDelegateDecorator.configured(configuration);
return asStringHeaders(headers, RuntimeDelegateDecorator.configured(configuration));
}

/**
* Returns string view of passed headers. Any modifications to the headers are visible to the view, the view also
* supports removal of elements. Does not support other modifications.
*
* @param headers headers.
* @param rd {@link RuntimeDelegate} instance or {@code null} (in that case {@link RuntimeDelegate#getInstance()}
* will be called for before conversion of elements).
* @return String view of headers or {@code null} if {code headers} input parameter is {@code null}.
*/
public static MultivaluedMap<String, String> asStringHeaders(
final MultivaluedMap<String, Object> headers, RuntimeDelegate rd) {
if (headers == null) {
return null;
}

return new AbstractMultivaluedMap<String, String>(
Views.mapView(headers, input -> HeaderUtils.asStringList(input, rd))
) {
Expand Down
Loading

0 comments on commit 6108093

Please sign in to comment.