/*
* Copyright (C) 2009-2011 Open Wide
* Contact: contact@openwide.fr
*
* Licensed 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 fr.openwide.core.wicket.more.model;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.wicket.injection.Injector;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
import fr.openwide.core.jpa.business.generic.model.GenericEntity;
import fr.openwide.core.jpa.business.generic.model.GenericEntityReference;
import fr.openwide.core.jpa.business.generic.service.IEntityService;
import fr.openwide.core.jpa.util.HibernateUtils;
import fr.openwide.core.wicket.more.util.model.LoadableDetachableModelExtendedDebugInformation;
public class GenericEntityModel<K extends Serializable & Comparable<K>, E extends GenericEntity<K, ?>>
extends LoadableDetachableModel<E> {
private static final long serialVersionUID = 1L;
private static final Logger LOGGER = LoggerFactory.getLogger(GenericEntityModel.class);
private static final boolean EXTENDED_DEBUG_INFO;
static {
EXTENDED_DEBUG_INFO = LOGGER.isDebugEnabled();
if (EXTENDED_DEBUG_INFO) {
LOGGER.warn("Extended debug info for GenericEntityModel is enabled."
+ " This may cause a significant performance hit.");
}
}
@SuppressWarnings("unchecked") // SerializableModelFactory works for any T extending GenericEntity<?, ?>
public static <E extends GenericEntity<?, ?>>
Function<E, GenericEntityModel<?, E>> factory() {
return (Function<E, GenericEntityModel<?, E>>) (Object) Factory.INSTANCE;
}
@SuppressWarnings({"rawtypes", "unchecked"}) // SerializableModelFactory works for any T extending Serializable
private enum Factory implements Function<GenericEntity, GenericEntityModel> {
INSTANCE;
@Override
public GenericEntityModel apply(GenericEntity input) {
return new GenericEntityModel(input);
}
}
@SpringBean
private IEntityService entityService;
private transient boolean attached = false;
private transient LoadableDetachableModelExtendedDebugInformation extendedDebugInformation;
private GenericEntityReference<K, E> persistedEntityReference;
/**
* L'objectif est ici de stocker les entités qui n'ont pas encore été persistées en base (typiquement, quand
* on fait la création).
*/
private E notYetPersistedEntity;
public static <K extends Serializable & Comparable<K>, E extends GenericEntity<K, ?>> GenericEntityModel<K, E> of(E entity) {
return new GenericEntityModel<K, E>(entity);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <E extends GenericEntity<?, ?>> GenericEntityModel<?, E> ofUnknownIdType(E entity) {
return (GenericEntityModel<?, E>)new GenericEntityModel(entity);
}
/**
* Construit un GenericEntityModel dont l'objet est <code>null</code> et qui n'est pas attaché.
*/
public GenericEntityModel() {
Injector.get().inject(this);
if (EXTENDED_DEBUG_INFO) {
this.extendedDebugInformation = new LoadableDetachableModelExtendedDebugInformation();
} else {
this.extendedDebugInformation = null;
}
}
public GenericEntityModel(E entity) {
this();
setObject(entity);
}
@Override
protected E load() {
E result = null;
if (persistedEntityReference != null) {
result = HibernateUtils.unwrap(entityService.getEntity(persistedEntityReference));
} else {
result = notYetPersistedEntity;
}
attached = true;
if (EXTENDED_DEBUG_INFO) {
extendedDebugInformation.onAttach();
}
return result;
}
@Override
public void setObject(E entity) {
E persistentObject = HibernateUtils.unwrap(entity);
super.setObject(persistentObject);
attached = true;
if (EXTENDED_DEBUG_INFO) {
extendedDebugInformation.onAttach();
}
updateSerializableData(); // Useful to keep equals() and getId() up-to-date and for compatibility with old applications
}
protected K getId() {
return persistedEntityReference != null ? persistedEntityReference.getId() : null;
}
@Override
public void detach() {
if (EXTENDED_DEBUG_INFO) {
extendedDebugInformation.onDetach();
}
if (!attached) {
fixSerializableData();
return;
}
updateSerializableData();
super.detach();
attached = false;
}
/**
* Updates the serializable data (id, clazz, notYetPersistedEntity) according to the attached object's value.
* <p>Only called when the model is attached.
*/
private void updateSerializableData() {
E entity = super.getObject();
if (entity != null) {
if (entity.getId() != null) {
persistedEntityReference = GenericEntityReference.of(entity);
notYetPersistedEntity = null;
} else {
persistedEntityReference = null;
notYetPersistedEntity = entity;
}
} else {
persistedEntityReference = null;
notYetPersistedEntity = null;
}
}
/**
* If the entity has been persisted since this model has been detached, then fix the serializable data
* (this may happen if two models reference the same non-persisted entity, for instance)
*/
private void fixSerializableData() {
if (notYetPersistedEntity != null && notYetPersistedEntity.getId() != null) {
persistedEntityReference = GenericEntityReference.of(notYetPersistedEntity);
notYetPersistedEntity = null;
}
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (!(obj instanceof GenericEntityModel)) {
return false;
}
GenericEntityModel<?, ?> other = (GenericEntityModel<?, ?>) obj;
return new EqualsBuilder()
.append(persistedEntityReference, other.persistedEntityReference)
.append(notYetPersistedEntity, other.notYetPersistedEntity)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder()
.append(persistedEntityReference)
.append(notYetPersistedEntity)
.toHashCode();
}
private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
in.defaultReadObject();
if (EXTENDED_DEBUG_INFO) {
this.extendedDebugInformation = new LoadableDetachableModelExtendedDebugInformation();
}
}
private void writeObject(ObjectOutputStream out) throws IOException {
if (attached) {
LOGGER.warn(
"Serializing an attached GenericEntityModel with persistedEntityReference={} and notYetPersistedEntity={}",
persistedEntityReference, notYetPersistedEntity
);
if (EXTENDED_DEBUG_INFO) {
LOGGER.debug(
"StackTrace from the latest attach (setObject() or load()): \n{}",
extendedDebugInformation.getLatestAttachInformation()
);
}
}
out.defaultWriteObject();
}
}