/*
* 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 org.apache.wicket.model;
import org.danekja.java.util.function.serializable.SerializableSupplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Model that makes working with detachable models a breeze. LoadableDetachableModel holds a
* temporary, transient model object, that is set when {@link #getObject()} is called by calling
* abstract method 'load', and that will be reset/ set to null on {@link #detach()}.
*
* A usage example:
*
* <pre>
* LoadableDetachableModel venueListModel = new LoadableDetachableModel()
* {
* protected Object load()
* {
* return getVenueDao().findVenues();
* }
* };
* </pre>
*
* <p>
* Though you can override methods {@link #onAttach()} and {@link #onDetach()} for additional
* attach/ detach behavior, the point of this class is to hide as much of the attaching/ detaching
* as possible. So you should rarely need to override those methods, if ever.
* </p>
*
* @author Eelco Hillenius
* @author Igor Vaynberg
*
* @param <T>
* The Model Object type
*/
public abstract class LoadableDetachableModel<T> implements IModel<T>
{
private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(LoadableDetachableModel.class);
/** Internal state of the LoadableDetachableModel. */
private enum InternalState {
DETACHED, ATTACHING, ATTACHED;
@Override
public String toString()
{
return name().toLowerCase();
}
}
/** Keeps track of whether this model is attached or detached */
private transient InternalState state = InternalState.DETACHED;
/** temporary, transient object. */
private transient T transientModelObject;
/**
* Default constructor, constructs the model in detached state with no data associated with the
* model.
*/
public LoadableDetachableModel()
{
}
/**
* This constructor is used if you already have the object retrieved and want to wrap it with a
* detachable model. Constructs the model in attached state. Calls to {@link #getObject()} will
* return {@code object} until {@link #detach()} is called.
*
* @param object
* retrieved instance of the detachable object
*/
public LoadableDetachableModel(T object)
{
this.transientModelObject = object;
state = InternalState.ATTACHED;
}
@Override
public void detach()
{
// even if LDM is in partial attached state (ATTACHING) it should be detached
if (state != null && state != InternalState.DETACHED)
{
try
{
onDetach();
}
finally
{
state = InternalState.DETACHED;
transientModelObject = null;
log.debug("removed transient object for '{}'", this);
}
}
}
@Override
public final T getObject()
{
if (state == null || state == InternalState.DETACHED)
{
// prevent infinite attachment loops
state = InternalState.ATTACHING;
transientModelObject = load();
if (log.isDebugEnabled())
{
log.debug("loaded transient object '{}' for '{}'", transientModelObject, this);
}
state = InternalState.ATTACHED;
onAttach();
}
return transientModelObject;
}
/**
* Gets the attached status of this model instance
*
* @return true if the model is attached, false otherwise
*/
public final boolean isAttached()
{
return state == InternalState.ATTACHED;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder(super.toString());
sb.append(":attached=")
.append(isAttached())
.append(":tempModelObject=[")
.append(this.transientModelObject)
.append(']');
return sb.toString();
}
/**
* Loads and returns the (temporary) model object.
*
* @return the (temporary) model object
*/
protected abstract T load();
/**
* Attaches to the current request. Implement this method with custom behavior, such as loading
* the model object.
*/
protected void onAttach()
{
}
/**
* Detaches from the current request. Implement this method with custom behavior, such as
* setting the model object to null.
*/
protected void onDetach()
{
}
/**
* Manually loads the model with the specified object. Subsequent calls to {@link #getObject()}
* will return {@code object} until {@link #detach()} is called.
*
* @param object
* The object to set into the model
*/
@Override
public void setObject(final T object)
{
state = InternalState.ATTACHED;
transientModelObject = object;
}
/**
* Create a {@link LoadableDetachableModel} for the given supplier.
*
* @param <T>
* @param getter Used for the getObject() method.
* @return the model
*/
public static <T> LoadableDetachableModel<T> of(SerializableSupplier<T> getter)
{
return new LoadableDetachableModel<T>()
{
private static final long serialVersionUID = 1L;
@Override
protected T load()
{
return getter.get();
}
};
}
}