Skip to content

Commit

Permalink
[Blazebit#1805] Add logic to EntityViewManager and ProxyFactory to ma…
Browse files Browse the repository at this point in the history
…intain evm references in proxies
  • Loading branch information
Mobe91 committed Oct 1, 2023
1 parent a9b1b6e commit 72a12e4
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,11 @@ public class EntityViewManagerProducer {
evm = config.createEntityViewManager(criteriaBuilderFactory);
}
@PreDestroy
public void closeEvm() {
evm.close();
}
@Produces
@ApplicationScoped
public EntityViewManager createEntityViewManager() {
Expand Down Expand Up @@ -377,6 +382,11 @@ public class EntityViewManagerProducer {
evm = config.createEntityViewManager(criteriaBuilderFactory);
}
@PreDestroy
public void closeEvm() {
evm.close();
}
@Produces
@ApplicationScoped
public EntityViewManager createEntityViewManager() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@

/**
* An interface that gives access to the metamodel and object builders.
* <p>
* There are currently limitations when it comes to using multiple instances of {@link EntityViewManager}
* simultaneously in an application. Specifically, multiple instances of {@link EntityViewManager} running within
* in the same class loader may not share entity view types. This is because the {@link EntityViewManager}
* dynamically creates proxy classes for entity view types and injects static self-references into these proxies.
* Another {@link EntityViewManager} creating a proxy for the same entity view type may find that the proxy class
* already exists within the class loader in which case it will overwrite the static self-references to point to
* itself leading to undefined behavior in the other {@link EntityViewManager}.
*
* @author Christian Beikov
* @since 1.0.0
Expand Down Expand Up @@ -502,4 +510,12 @@ public interface EntityViewManager extends ServiceProvider {
* @since 1.2.0
*/
public <T, Q extends FullQueryBuilder<T, Q>> Q applySetting(EntityViewSetting<T, Q> setting, CriteriaBuilder<?> criteriaBuilder, String entityViewRoot);

/**
* Closes this {@link EntityViewManager} and frees resources. The behavior of any method called on an entity
* view manager after this method has been invoked is undefined.
*
* @since 1.6.10
*/
public void close();
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class SerializableEntityViewManager implements EntityViewManager, Seriali

public static final String EVM_FIELD_NAME = "ENTITY_VIEW_MANAGER";
public static final String SERIALIZABLE_EVM_FIELD_NAME = "SERIALIZABLE_ENTITY_VIEW_MANAGER";
public static final String SERIALIZABLE_EVM_DELEGATE_FIELD_NAME = "evm";

private final Class<?> entityViewClass;
private transient volatile EntityViewManager evm;
Expand Down Expand Up @@ -275,4 +276,9 @@ public <T, Q extends FullQueryBuilder<T, Q>> Q applySetting(EntityViewSetting<T,
public <T> T getService(Class<T> serviceClass) {
return getEvm().getService(serviceClass);
}

@Override
public void close() {
getEvm().close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,11 @@ public EntityViewManager getSerializableDelegate(Class<?> entityViewClass) {
return serializableDelegates.get(proxyFactory.getProxy(this, metamodel.managedViewOrError(entityViewClass)));
}

@Override
public void close() {
proxyFactory.clear();
}

/**
* @author Christian Beikov
* @since 1.2.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,8 @@ private <T> Class<? extends T> defineOrGetClass(Class<?> clazz, Class<?> neighbo
}

private <T> Class<? extends T> defineOrGetClass(EntityViewManager entityViewManager, boolean unsafe, Class<?> clazz, Class<?> neighbourClazz, CtClass cc) throws IOException, IllegalAccessException, NoSuchFieldException, CannotCompileException {
Class<? extends T> c;
boolean newlyDefined = false;
try {
// Ask the package opener to allow deep access, otherwise defining the class will fail
if (clazz.getPackage() != null) {
Expand All @@ -1021,13 +1023,8 @@ private <T> Class<? extends T> defineOrGetClass(EntityViewManager entityViewMana
cc.writeFile(DEBUG_DUMP_DIRECTORY.toString());
}

Class<? extends T> c = (Class<? extends T>) UnsafeHelper.define(cc.getName(), cc.toBytecode(), neighbourClazz);

if (entityViewManager != null) {
c.getField(SerializableEntityViewManager.EVM_FIELD_NAME).set(null, entityViewManager);
}

return c;
c = (Class<? extends T>) UnsafeHelper.define(cc.getName(), cc.toBytecode(), neighbourClazz);
newlyDefined = true;
} catch (CannotCompileException | LinkageError ex) {
// If there are multiple proxy factories for the same class loader
// we could end up in defining a class multiple times, so we check if the classloader
Expand All @@ -1037,7 +1034,7 @@ private <T> Class<? extends T> defineOrGetClass(EntityViewManager entityViewMana
|| ex.getCause() instanceof InvocationTargetException && ex.getCause().getCause() instanceof LinkageError && (error = (LinkageError) ex.getCause().getCause()) != null
|| ex.getCause() instanceof LinkageError && (error = (LinkageError) ex.getCause()) != null) {
try {
return (Class<? extends T>) pool.getClassLoader().loadClass(cc.getName());
c = (Class<? extends T>) pool.getClassLoader().loadClass(cc.getName());
} catch (ClassNotFoundException cnfe) {
// Something we can't handle happened
throw error;
Expand All @@ -1049,12 +1046,33 @@ private <T> Class<? extends T> defineOrGetClass(EntityViewManager entityViewMana
// With Java 9 it's actually the case that Javassist doesn't throw the LinkageError but instead tries to define the class differently
// Too bad that this different path lead to a NullPointerException
try {
return (Class<? extends T>) pool.getClassLoader().loadClass(cc.getName());
c = (Class<? extends T>) pool.getClassLoader().loadClass(cc.getName());
} catch (ClassNotFoundException cnfe) {
// Something we can't handle happened
throw ex;
}
}
if (entityViewManager != null) {
updateEvmReferences(c, entityViewManager, !newlyDefined);
}
return c;
}

private void updateEvmReferences(Class<?> entityViewClass, EntityViewManager evm, boolean updateSerializableEvmDelegate) {
try {
entityViewClass.getField(SerializableEntityViewManager.EVM_FIELD_NAME).set(null, evm);
if (updateSerializableEvmDelegate) {
SerializableEntityViewManager serializableEvm =
(SerializableEntityViewManager) entityViewClass.getField(
SerializableEntityViewManager.SERIALIZABLE_EVM_FIELD_NAME).get(null);
Field serializableEvmDelegateField = SerializableEntityViewManager.class.getDeclaredField(
SerializableEntityViewManager.SERIALIZABLE_EVM_DELEGATE_FIELD_NAME);
serializableEvmDelegateField.setAccessible(true);
serializableEvmDelegateField.set(serializableEvm, evm);
}
} catch (IllegalAccessException | NoSuchFieldException e) {
throw new RuntimeException(e);
}
}

private boolean shouldAddDefaultConstructor(boolean hasEmptyConstructor, boolean addedReferenceConstructor, CtField[] attributeFields) {
Expand Down Expand Up @@ -3489,4 +3507,17 @@ private String getGenericSignature(MethodAttribute<?, ?> attribute, CtField attr

return sb.toString();
}

public void clear() {
for (Class<?> proxyClass : proxyClasses.values()) {
updateEvmReferences(proxyClass, null, true);
}
proxyClasses.clear();
for (Class<?> unsafeProxyClass : unsafeProxyClasses.values()) {
updateEvmReferences(unsafeProxyClass, null, true);
}
unsafeProxyClasses.clear();
baseClasses.clear();
proxyClassesToViewClasses.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public EntityViewManager build(EntityViewConfiguration cfg, Class<?>... classes)
EntityViewManagerFactoryCacheKey cacheKey = new EntityViewManagerFactoryCacheKey(cbf, cfg);
EntityViewManager evm;
if ((evm = evmCache.get(cacheKey)) == null) {
evm = build0(cfg, classes);
evm = build0(cfg);
evmCache.put(cacheKey, evm);
}
AbstractEntityViewTest.evm = evm;
Expand Down Expand Up @@ -100,7 +100,7 @@ public EntityViewManager build(EntityViewConfiguration cfg, Class<?>... classes)
return evm;
}

private EntityViewManager build0(EntityViewConfiguration cfg, Class<?>[] classes) {
private EntityViewManager build0(EntityViewConfiguration cfg) {
return cfg.createEntityViewManager(cbf);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.blazebit.persistence.view.ConfigurationProperties;
import com.blazebit.persistence.view.EntityViewManager;
import com.blazebit.persistence.view.EntityViews;
import com.blazebit.persistence.view.SerializableEntityViewManager;
import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor;
import com.blazebit.persistence.view.impl.metamodel.MappingConstructorImpl;
import com.blazebit.persistence.view.impl.proxy.ObjectInstantiator;
Expand Down Expand Up @@ -337,11 +338,64 @@ public void testProxyToStringFlatView() throws Exception {
}

@Test
public void testMutableProxyWithPrimitiveArray() throws Exception {
public void testMutableProxyWithPrimitiveArray() {
ManagedViewType<DocumentCreateViewWithPrimitiveArray> viewType = build(entityViewConfiguration, DocumentCreateViewWithPrimitiveArray.class).getMetamodel().managedView(DocumentCreateViewWithPrimitiveArray.class);
proxyFactory.getProxy(evm, (ManagedViewTypeImplementor<DocumentCreateViewWithPrimitiveArray>) viewType);
}

@Test
public void close() throws NoSuchFieldException, IllegalAccessException {
// Given
EntityViewManager evm = build(entityViewConfiguration, DocumentCreateViewWithPrimitiveArray.class,
DocumentInterfaceView.class);
ManagedViewType<DocumentCreateViewWithPrimitiveArray> documentCreateViewWithPrimitiveArrayType = evm.getMetamodel()
.managedView(DocumentCreateViewWithPrimitiveArray.class);
ManagedViewType<DocumentInterfaceView> documentInterfaceViewType = evm.getMetamodel()
.managedView(DocumentInterfaceView.class);
Class<? extends DocumentCreateViewWithPrimitiveArray> documentCreateViewWithPrimitiveArrayProxyClass = proxyFactory.getProxy(
AbstractEntityViewTest.evm, (ManagedViewTypeImplementor<DocumentCreateViewWithPrimitiveArray>) documentCreateViewWithPrimitiveArrayType);
Class<? extends DocumentInterfaceView> documentInterfaceViewProxyClass = proxyFactory.getProxy(
AbstractEntityViewTest.evm, (ManagedViewTypeImplementor<DocumentInterfaceView>) documentInterfaceViewType);

// When
proxyFactory.clear();

// Then
assertProxyEvmReferencesNull(documentCreateViewWithPrimitiveArrayProxyClass);
assertProxyEvmReferencesNull(documentInterfaceViewProxyClass);
Map<Class<?>, Class<?>> baseClasses = getDeclaredFieldValue(proxyFactory, "unsafeProxyClasses", Map.class);
assertTrue(baseClasses.isEmpty());
Map<Class<?>, Class<?>> proxyClasses = getDeclaredFieldValue(proxyFactory, "unsafeProxyClasses", Map.class);
assertTrue(proxyClasses.isEmpty());
Map<Class<?>, Class<?>> unsafeProxyClasses = getDeclaredFieldValue(proxyFactory, "unsafeProxyClasses", Map.class);
assertTrue(unsafeProxyClasses.isEmpty());
Map<Class<?>, Class<?>> proxyClassesToViewClasses = getDeclaredFieldValue(proxyFactory, "proxyClassesToViewClasses", Map.class);
assertTrue(proxyClassesToViewClasses.isEmpty());
}

private void assertProxyEvmReferencesNull(Class<?> proxy) throws NoSuchFieldException, IllegalAccessException {
EntityViewManager proxyEvm = (EntityViewManager) proxy.getField(SerializableEntityViewManager.EVM_FIELD_NAME).get(null);
SerializableEntityViewManager serializableEvm =
(SerializableEntityViewManager) proxy.getField(
SerializableEntityViewManager.SERIALIZABLE_EVM_FIELD_NAME).get(null);
Field serializableEvmDelegateField = SerializableEntityViewManager.class.getDeclaredField(
SerializableEntityViewManager.SERIALIZABLE_EVM_DELEGATE_FIELD_NAME);
serializableEvmDelegateField.setAccessible(true);
EntityViewManager serializableEvmDelegate = (EntityViewManager) serializableEvmDelegateField.get(serializableEvm);
assertNull(proxyEvm);
assertNull(serializableEvmDelegate);
}

private <T> T getDeclaredFieldValue(Object obj, String fieldName, Class<T> resultType) {
try {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return (T) field.get(obj);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}

private void assertAttribute(Class<?> proxyClass, String fieldName, int modifiers, Class<?> type, Class<?>... typeArguments) throws Exception {
assertField(proxyClass, fieldName, modifiers, type, typeArguments);
assertGetter(proxyClass, fieldName, type, typeArguments);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -542,5 +542,10 @@ public <T, Q extends FullQueryBuilder<T, Q>> Q applySetting(EntityViewSetting<T,
public <T> T getService(Class<T> serviceClass) {
return entityViewManager.get().getService(serviceClass);
}

@Override
public void close() {
entityViewManager.get().close();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -539,5 +539,10 @@ public <T, Q extends FullQueryBuilder<T, Q>> Q applySetting(EntityViewSetting<T,
public <T> T getService(Class<T> serviceClass) {
return entityViewManager.get().getService(serviceClass);
}

@Override
public void close() {
entityViewManager.get().close();
}
}
}

0 comments on commit 72a12e4

Please sign in to comment.