Skip to content
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
4 changes: 2 additions & 2 deletions src/main/java/ognl/ASTProperty.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public int getIndexedPropertyType(OgnlContext context, Object source)
if (property instanceof String) {
return OgnlRuntime.getIndexedPropertyType((source == null)
? null
: OgnlRuntime.getCompiler().getInterfaceClass(source.getClass()), (String) property);
: OgnlRuntime.getInterfaceClass(source.getClass()), (String) property);
}
}

Expand Down Expand Up @@ -447,7 +447,7 @@ public String toSetSourceString(OgnlContext context, Object target) {
}
}

PropertyDescriptor pd = OgnlRuntime.getPropertyDescriptor(OgnlRuntime.getCompiler().getInterfaceClass(context.getCurrentObject().getClass()), name);
PropertyDescriptor pd = OgnlRuntime.getPropertyDescriptor(OgnlRuntime.getInterfaceClass(context.getCurrentObject().getClass()), name);

if (pd != null) {
Method pdMethod = lastChild(context) ? pd.getWriteMethod() : pd.getReadMethod();
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/ognl/OgnlCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,13 @@
cacheFactory.createCache(new MethodAccessCacheEntryFactory());

private final MethodPermCacheEntryFactory methodPermCacheEntryFactory =
new MethodPermCacheEntryFactory(System.getSecurityManager());

Check warning on line 157 in src/main/java/ognl/OgnlCache.java

View workflow job for this annotation

GitHub Actions / build (17)

getSecurityManager() in java.lang.System has been deprecated and marked for removal

Check warning on line 157 in src/main/java/ognl/OgnlCache.java

View workflow job for this annotation

GitHub Actions / build (21)

getSecurityManager() in java.lang.System has been deprecated and marked for removal

Check warning on line 157 in src/main/java/ognl/OgnlCache.java

View workflow job for this annotation

GitHub Actions / Scan

getSecurityManager() in java.lang.System has been deprecated and marked for removal

private final Cache<Method, Boolean> methodPermCache = cacheFactory.createCache(methodPermCacheEntryFactory);

private final ClassCache<Class<?>> interfaceClassCache =
cacheFactory.createClassCache(key -> OgnlRuntime.getCompiler().getInterfaceClass(key));

public Class<?>[] getMethodParameterTypes(Method method) throws CacheException {
return methodParameterTypesCache.get(method);
}
Expand All @@ -182,6 +185,14 @@
return propertyDescriptorCache.get(clazz);
}

public Class<?> getInterfaceClass(Class<?> clazz) throws CacheException {
return interfaceClassCache.get(clazz);
}

void clearInterfaceClassCache() {
interfaceClassCache.clear();
}

/**
* @deprecated since 3.4.6, forRemoval = true
*/
Expand Down Expand Up @@ -227,6 +238,7 @@
// _methodCache.setClassInspector( inspector );
// _invokePermissionCache.setClassInspector( inspector );
fieldCache.setClassInspector(inspector);
interfaceClassCache.setClassInspector(inspector);
}

public Class<?>[] getGenericMethodParameterTypes(GenericMethodParameterTypeCacheEntry key) throws CacheException {
Expand Down Expand Up @@ -255,6 +267,7 @@
invokePermissionCache.clear();
fieldCache.clear();
methodAccessCache.clear();
interfaceClassCache.clear();
}

public ElementsAccessor getElementsAccessor(Class<?> clazz) throws OgnlException {
Expand Down Expand Up @@ -285,7 +298,7 @@
* @deprecated since 3.4.6, forRemoval = true
*/
@Deprecated
public void setSecurityManager(SecurityManager securityManager) {

Check warning on line 301 in src/main/java/ognl/OgnlCache.java

View workflow job for this annotation

GitHub Actions / build (17)

java.lang.SecurityManager in java.lang has been deprecated and marked for removal

Check warning on line 301 in src/main/java/ognl/OgnlCache.java

View workflow job for this annotation

GitHub Actions / build (21)

java.lang.SecurityManager in java.lang has been deprecated and marked for removal

Check warning on line 301 in src/main/java/ognl/OgnlCache.java

View workflow job for this annotation

GitHub Actions / Scan

java.lang.SecurityManager in java.lang has been deprecated and marked for removal
methodPermCacheEntryFactory.setSecurityManager(securityManager);
}

Expand Down
14 changes: 14 additions & 0 deletions src/main/java/ognl/OgnlRuntime.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.security.AccessControlContext;

Check warning on line 47 in src/main/java/ognl/OgnlRuntime.java

View workflow job for this annotation

GitHub Actions / build (17)

java.security.AccessControlContext in java.security has been deprecated and marked for removal

Check warning on line 47 in src/main/java/ognl/OgnlRuntime.java

View workflow job for this annotation

GitHub Actions / build (21)

java.security.AccessControlContext in java.security has been deprecated and marked for removal

Check warning on line 47 in src/main/java/ognl/OgnlRuntime.java

View workflow job for this annotation

GitHub Actions / Scan

java.security.AccessControlContext in java.security has been deprecated and marked for removal
import java.security.AccessController;

Check warning on line 48 in src/main/java/ognl/OgnlRuntime.java

View workflow job for this annotation

GitHub Actions / build (17)

java.security.AccessController in java.security has been deprecated and marked for removal

Check warning on line 48 in src/main/java/ognl/OgnlRuntime.java

View workflow job for this annotation

GitHub Actions / build (21)

java.security.AccessController in java.security has been deprecated and marked for removal

Check warning on line 48 in src/main/java/ognl/OgnlRuntime.java

View workflow job for this annotation

GitHub Actions / Scan

java.security.AccessController in java.security has been deprecated and marked for removal
import java.security.Permission;
import java.security.Permissions;
import java.security.PrivilegedActionException;
Expand Down Expand Up @@ -557,12 +557,26 @@

public static void setCompiler(OgnlExpressionCompiler compiler) {
_compiler = compiler;
// The cached interface classes are answers of the previous compiler
cache.clearInterfaceClassCache();
}

public static OgnlExpressionCompiler getCompiler() {
return _compiler;
}

/**
* Same as calling {@link OgnlExpressionCompiler#getInterfaceClass(Class)} on the current compiler, but
* remembering the result for each class in the {@link OgnlCache}. The answer only depends on the class and
* on the compiler, and interpreted evaluation asks for it on every link of a property chain.
*
* @param clazz The class to find a compatible interface for.
* @return what the current compiler's {@code getInterfaceClass(clazz)} returns.
*/
static Class<?> getInterfaceClass(Class<?> clazz) {
return cache.getInterfaceClass(clazz);
}

public static void compileExpression(OgnlContext context, Node expression, Object root)
throws Exception {
_compiler.compileExpression(context, expression, root);
Expand Down
166 changes: 166 additions & 0 deletions src/test/java/ognl/OgnlRuntimeInterfaceClassCacheTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 ognl;

import ognl.enhance.ExpressionCompiler;
import ognl.enhance.OgnlExpressionCompiler;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;

/**
* Issue #650: the interface class of a chain link's source is looked up once per class.
*/
class OgnlRuntimeInterfaceClassCacheTest {

public static class Customer {
public String getName() {
return "Alice";
}
}

public static class Order {
private final Customer customer = new Customer();

public Customer getCustomer() {
return customer;
}
}

private static class CountingCompiler extends ExpressionCompiler {

private final ConcurrentHashMap<Class<?>, AtomicInteger> calls = new ConcurrentHashMap<>();

@Override
public Class<?> getInterfaceClass(Class<?> clazz) {
calls.computeIfAbsent(clazz, c -> new AtomicInteger()).incrementAndGet();
return super.getInterfaceClass(clazz);
}

int calls(Class<?> clazz) {
AtomicInteger count = calls.get(clazz);
return count == null ? 0 : count.get();
}
}

private static class ConstantCompiler extends ExpressionCompiler {

@Override
public Class<?> getInterfaceClass(Class<?> clazz) {
return Object.class;
}
}

private OgnlExpressionCompiler originalCompiler;
private CountingCompiler compiler;

@BeforeEach
void setUp() {
originalCompiler = OgnlRuntime.getCompiler();
compiler = new CountingCompiler();
OgnlRuntime.setCompiler(compiler);
}

@AfterEach
void tearDown() {
OgnlRuntime.setClassCacheInspector(null);
OgnlRuntime.setCompiler(originalCompiler);
}

@Test
void chainLooksUpInterfaceClassOncePerClass() throws Exception {
Order order = new Order();
OgnlContext context = Ognl.createDefaultContext(order, new DefaultMemberAccess(false));
Object tree = Ognl.parseExpression("customer.name");

for (int i = 0; i < 5; i++) {
assertEquals("Alice", Ognl.getValue(tree, context, order));
}

assertEquals(1, compiler.calls(Order.class));
}

@Test
void returnsWhatTheCompilerReturns() {
assertSame(List.class, OgnlRuntime.getInterfaceClass(ArrayList.class));
assertSame(List.class, OgnlRuntime.getInterfaceClass(ArrayList.class));
assertSame(Order.class, OgnlRuntime.getInterfaceClass(Order.class));
assertSame(Order.class, OgnlRuntime.getInterfaceClass(Order.class));

assertEquals(1, compiler.calls(ArrayList.class));
assertEquals(1, compiler.calls(Order.class));
}

@Test
void newCompilerIsAskedAgain() {
assertSame(Order.class, OgnlRuntime.getInterfaceClass(Order.class));

OgnlExpressionCompiler other = new ConstantCompiler();
OgnlRuntime.setCompiler(other);

assertSame(Object.class, OgnlRuntime.getInterfaceClass(Order.class));

OgnlRuntime.setCompiler(compiler);

assertSame(Order.class, OgnlRuntime.getInterfaceClass(Order.class));
assertEquals(2, compiler.calls(Order.class));
}

@Test
void classCacheInspectorCanKeepAClassOutOfTheCache() {
OgnlRuntime.setClassCacheInspector(type -> type != Order.class);

assertSame(Order.class, OgnlRuntime.getInterfaceClass(Order.class));
assertSame(Order.class, OgnlRuntime.getInterfaceClass(Order.class));
assertSame(Customer.class, OgnlRuntime.getInterfaceClass(Customer.class));
assertSame(Customer.class, OgnlRuntime.getInterfaceClass(Customer.class));

assertEquals(2, compiler.calls(Order.class));
assertEquals(1, compiler.calls(Customer.class));
}

@Test
void clearCacheDropsInterfaceClasses() {
OgnlRuntime.getInterfaceClass(Order.class);

OgnlRuntime.clearCache();
OgnlRuntime.getInterfaceClass(Order.class);

assertEquals(2, compiler.calls(Order.class));
}

@Test
void clearAdditionalCacheDropsInterfaceClasses() {
OgnlRuntime.getInterfaceClass(Order.class);

OgnlRuntime.clearAdditionalCache();
OgnlRuntime.getInterfaceClass(Order.class);

assertEquals(2, compiler.calls(Order.class));
}

}
Loading