Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add caching and improve performance #5205

Merged
merged 1 commit into from
Dec 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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