package fr.openwide.core.wicket.more.model.threadsafe.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import org.apache.wicket.Session;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fr.openwide.core.wicket.more.model.threadsafe.SessionThreadSafeDerivedSerializableStateLoadableDetachableModel;
import fr.openwide.core.wicket.more.model.threadsafe.SessionThreadSafeSimpleLoadableDetachableModel;
import fr.openwide.core.wicket.more.util.model.LoadableDetachableModelExtendedDebugInformation;
/**
* An alternative implementation of {@link LoadableDetachableModel} that is thread-safe, and may thus be used in multiple request cycles at the same time.
* <p>This class should be used as an abstract base class for loadable/detachable models when the models are stored in a global object, such as the {@link Session wicket session}.
* <p>Two subclasses are provided to ease implementation : {@link SessionThreadSafeSimpleLoadableDetachableModel} (simple use) and {@link SessionThreadSafeDerivedSerializableStateLoadableDetachableModel} (advanced use).
*
* @param <T> The type of the model object.
*/
public abstract class AbstractThreadSafeLoadableDetachableModel<T, TThreadContext extends LoadableDetachableModelThreadContext<T>>
implements IModel<T> {
private static final long serialVersionUID = 6859907414385876596L;
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractThreadSafeLoadableDetachableModel.class);
private static final boolean EXTENDED_DEBUG_INFO;
static {
EXTENDED_DEBUG_INFO = LOGGER.isDebugEnabled();
if (EXTENDED_DEBUG_INFO) {
LOGGER.warn("Extended debug info for AbstractThreadSafeLoadableDetachableModel is enabled."
+ " This may cause a significant performance hit.");
}
}
private transient LoadableDetachableModelExtendedDebugInformation extendedDebugInformation;
/**
* The loading context, local to each thread.
* <p>Will be null if the model is not currently attached.
*/
private transient ThreadLocal<TThreadContext> threadLocal = new ThreadLocal<TThreadContext>();
public AbstractThreadSafeLoadableDetachableModel() {
if (EXTENDED_DEBUG_INFO) {
this.extendedDebugInformation = new LoadableDetachableModelExtendedDebugInformation();
}
}
public AbstractThreadSafeLoadableDetachableModel(T object) {
this();
setObject(object);
}
private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
in.defaultReadObject();
threadLocal = new ThreadLocal<>();
if (EXTENDED_DEBUG_INFO) {
this.extendedDebugInformation = new LoadableDetachableModelExtendedDebugInformation();
}
}
private void writeObject(ObjectOutputStream out) throws IOException {
TThreadContext threadContext = threadLocal.get();
if (threadContext == null) {
threadLocal.remove(); // Revert the side-effet of the call to get()
} else {
LOGGER.warn(
"Serializing an attached AbstractThreadSafeLoadableDetachableModel with threadContext={}",
threadContext
);
if (EXTENDED_DEBUG_INFO) {
LOGGER.debug(
"StackTrace from the latest attach (setObject() or load()): \n{}",
extendedDebugInformation.getLatestAttachInformation()
);
}
}
out.defaultWriteObject();
}
protected abstract TThreadContext newThreadContext();
@Override
public final T getObject() {
TThreadContext threadContext = threadLocal.get();
if (threadContext == null) { // If not attached yet
threadContext = newThreadContext();
threadLocal.set(threadContext); // Attach model
threadContext.setTransientModelObject(load(threadContext)); // Populate model value
if (EXTENDED_DEBUG_INFO) {
extendedDebugInformation.onAttach();
}
}
return threadContext.getTransientModelObject();
}
@Override
public final void setObject(T object) {
TThreadContext threadContext = threadLocal.get();
if (threadContext == null) { // If not attached yet
threadContext = newThreadContext();
threadLocal.set(threadContext); // Attach model
if (EXTENDED_DEBUG_INFO) {
extendedDebugInformation.onAttach();
}
}
threadContext.setTransientModelObject(wrap(object)); // Populate model value
onSetObject(threadContext);
}
/**
* Updates the threadContext as needed.
*/
protected abstract void onSetObject(TThreadContext threadContext);
/**
* Loads the model object value from the implementation-defined data source, updating the threadContext as needed.
*/
protected abstract T load(TThreadContext threadContext);
/**
* Wraps the model object value when the model is {@link #setObject(Object) set} , if needed.
* <p>This method is not called when the model object value is obtained using {@link #load()}.
*/
protected T wrap(T object) {
return object;
}
@Override
public final void detach() {
try {
if (EXTENDED_DEBUG_INFO) {
extendedDebugInformation.onDetach();
}
TThreadContext threadContext = threadLocal.get();
if (threadContext != null) { // If attached
onDetach(threadContext);
} else {
onDetachDetached();
}
} finally {
threadLocal.remove();
}
}
protected abstract void onDetach(TThreadContext threadContext);
/**
* Perform any necessary adjustment on implementation-defined detached data when detach() is called, but the
* model is already detached.
*/
protected abstract void onDetachDetached();
}