/*
Copyright 2011-2014 Red Hat, Inc
This file is part of PressGang CCMS.
PressGang CCMS is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
PressGang CCMS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with PressGang CCMS. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jboss.pressgang.ccms.server.rest.v1.factory.base;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import org.jboss.pressgang.ccms.model.base.AuditedEntity;
import org.jboss.pressgang.ccms.model.base.PressGangEntity;
import org.jboss.pressgang.ccms.rest.v1.collections.base.RESTBaseEntityCollectionItemV1;
import org.jboss.pressgang.ccms.rest.v1.collections.base.RESTBaseEntityCollectionV1;
import org.jboss.pressgang.ccms.rest.v1.collections.base.RESTUpdateCollectionItemV1;
import org.jboss.pressgang.ccms.rest.v1.entities.base.RESTBaseAuditedEntityV1;
import org.jboss.pressgang.ccms.rest.v1.entities.base.RESTBaseEntityV1;
import org.jboss.pressgang.ccms.rest.v1.expansion.ExpandDataTrunk;
import org.jboss.pressgang.ccms.server.rest.DatabaseOperation;
import org.jboss.pressgang.ccms.server.rest.v1.EntityCache;
import org.jboss.pressgang.ccms.server.rest.v1.RESTChangeAction;
import org.jboss.pressgang.ccms.server.rest.v1.RESTSingleChangeAction;
import org.jboss.pressgang.ccms.server.rest.v1.factory.LogDetailsV1Factory;
import org.jboss.pressgang.ccms.server.utils.EntityManagerWrapper;
import org.jboss.pressgang.ccms.server.utils.EnversUtilities;
import org.jboss.resteasy.spi.BadRequestException;
import org.jboss.resteasy.spi.InternalServerErrorException;
/**
* Defines a factory that can create REST entity objects from JPA entities, and update JPA entities from REST entities
*
* @param <T> The REST object type
* @param <U> The database object type
* @param <V> The REST object collection type
* @param <W> The REST object collection item type
*/
@SuppressWarnings("unchecked")
public abstract class RESTEntityFactory<T extends RESTBaseEntityV1<T>, U extends PressGangEntity,
V extends RESTBaseEntityCollectionV1<T, V, W>, W extends RESTBaseEntityCollectionItemV1<T, V, W>> {
@Inject
protected EntityCache entityCache;
@Inject
protected EntityManagerWrapper entityManager;
@Inject
protected LogDetailsV1Factory logDetailsFactory;
/**
* Create a REST Entity representation from Database Entity specified by it's Primary Key.
*
* @param primaryKey The id of the database entity to use as the source for the REST entity
* @param baseUrl The REST url that was used to access this REST entity
* @param dataType The type of the returned data (XML or JSON)
* @param expand The Object that contains details about what fields should be expanded.
* @return A new REST entity populated with the values in a database entity
*/
public T createRESTEntityFromDBPK(final Object primaryKey, final String baseUrl, final String dataType, final ExpandDataTrunk expand,
final Number revision, final EntityManager entityManager) {
final U entity = entityManager.find(getDatabaseClass(), primaryKey);
if (entity == null) throw new BadRequestException("No entity was found with the id " + primaryKey);
return createRESTEntityFromDBEntity(entity, baseUrl, dataType, expand, revision);
}
/**
* Create a REST Entity representation from Database Entity.
*
* @param entity The entity that is to be transformed into a REST Entity.
* @param baseUrl The REST url that was used to access this REST entity
* @param dataType The type of the returned data (XML or JSON)
* @param expand The Object that contains details about what fields should be expanded.
* @return A new REST entity populated with the values in a database entity
*/
public T createRESTEntityFromDBEntity(final U entity, final String baseUrl, final String dataType, final ExpandDataTrunk expand) {
return createRESTEntityFromDBEntity(entity, baseUrl, dataType, expand, null, true);
}
/**
* Create a REST Entity representation from Database Entity.
*
* @param entity The entity that is to be transformed into a REST Entity.
* @param baseUrl The REST url that was used to access this REST entity
* @param dataType The type of the returned data (XML or JSON)
* @param expand The Object that contains details about what fields should be expanded.
* @param revision The revision number of the entity.
* @return A new REST entity populated with the values in a database entity
*/
public T createRESTEntityFromDBEntity(final U entity, final String baseUrl, final String dataType, final ExpandDataTrunk expand,
final Number revision) {
return createRESTEntityFromDBEntity(entity, baseUrl, dataType, expand, revision, true);
}
/**
* Create a REST Entity representation from Database Entity.
*
* @param entity The entity that is to be transformed into a REST Entity.
* @param baseUrl The REST url that was used to access this REST entity
* @param dataType The type of the returned data (XML or JSON)
* @param expand The Object that contains details about what fields should be expanded.
* @param revision The revision number of the entity.
* @param expandParentReferences If parent entities in children entities should be expanded.
* @return A new REST entity populated with the values in a database entity
*/
public T createRESTEntityFromDBEntity(final U entity, final String baseUrl, final String dataType, final ExpandDataTrunk expand,
final Number revision, final boolean expandParentReferences) {
final T retValue = createRESTEntityFromDBEntityInternal(entity, baseUrl, dataType, expand, revision, expandParentReferences);
if (entity instanceof AuditedEntity && retValue instanceof RESTBaseAuditedEntityV1) {
final AuditedEntity auditedEntity = (AuditedEntity) entity;
final RESTBaseAuditedEntityV1 auditedRestEntity = (RESTBaseAuditedEntityV1) retValue;
// Set the entities revision
final Number fixedRevision = revision == null ? EnversUtilities.getLatestRevision(entityManager,
auditedEntity) : EnversUtilities.getClosestRevision(entityManager, auditedEntity, revision);
auditedRestEntity.setRevision(fixedRevision.intValue());
// Add the log details
auditedRestEntity.setLogDetails(
logDetailsFactory.createRESTEntityFromAuditedEntity(auditedEntity, fixedRevision, RESTBaseAuditedEntityV1.LOG_DETAILS_NAME,
expand, dataType, baseUrl));
}
return retValue;
}
/**
* Create a REST Entity representation from Database Entity.
*
* @param entity The entity that is to be transformed into a REST Entity.
* @param baseUrl The REST url that was used to access this REST entity
* @param dataType The type of the returned data (XML or JSON)
* @param expand The Object that contains details about what fields should be expanded.
* @param revision The revision number of the entity.
* @param expandParentReferences If parent entities in children entities should be expanded.
* @return A new REST entity populated with the values in a database entity
*/
protected abstract T createRESTEntityFromDBEntityInternal(final U entity, final String baseUrl, final String dataType,
final ExpandDataTrunk expand, final Number revision, final boolean expandParentReferences);
/**
* Creates and returns a new database entity from a REST entity
*
* @param restEntity
* @return A new database entity
*/
public U createDBEntity(T restEntity) {
try {
final U dbEntity = getDatabaseClass().newInstance();
syncBaseDetails(dbEntity, restEntity);
return dbEntity;
} catch (InstantiationException e) {
throw new InternalServerErrorException(e);
} catch (IllegalAccessException e) {
throw new InternalServerErrorException(e);
}
}
public void syncDBEntityDeleteChanges(final PressGangEntity entity, final RESTBaseEntityV1<?> dataObject,
final RESTChangeAction<?> action) {
for (final RESTChangeAction<?> childAction : action.getDeleteChildActions()) {
doDeleteChildAction((U) entity, (T) dataObject, childAction);
}
// Update changes might have child delete items, so loop over them as well
for (final RESTChangeAction<?> childAction : action.getUpdateChildActions()) {
final PressGangEntity updateEntity = getChildEntityForAction((U) entity, (T) dataObject, childAction);
childAction.setDBEntity(updateEntity);
childAction.getFactory().syncDBEntityDeleteChanges(updateEntity, childAction.getRESTEntity(), childAction);
}
}
public void syncDBEntityCreateChanges(final PressGangEntity entity, final RESTBaseEntityV1<?> dataObject,
final RESTChangeAction<?> action) {
for (final RESTChangeAction<?> childAction : action.getCreateChildActions()) {
final PressGangEntity createdEntity = doCreateChildAction((U) entity, (T) dataObject, childAction);
childAction.setDBEntity(createdEntity);
childAction.getFactory().syncDBEntityCreateChanges(createdEntity, childAction.getRESTEntity(), childAction);
entityCache.addNew(childAction.getRESTEntity(), createdEntity);
}
// Update changes might have child create items, so loop over them as well
for (final RESTChangeAction<?> childAction : action.getUpdateChildActions()) {
final PressGangEntity updateEntity;
if (childAction.getDBEntity() == null) {
updateEntity = getChildEntityForAction((U) entity, (T) dataObject, childAction);
childAction.setDBEntity(updateEntity);
} else {
updateEntity = childAction.getDBEntity();
}
childAction.getFactory().syncDBEntityCreateChanges(updateEntity, childAction.getRESTEntity(), childAction);
}
}
public void syncDBEntityUpdateChanges(final PressGangEntity entity, final RESTBaseEntityV1<?> dataObject,
final RESTChangeAction<?> action) {
if (action.getType() == DatabaseOperation.UPDATE) {
syncBaseDetails((U) entity, (T) dataObject);
}
// We'll need to sync any additional content for create changes, so do it here
for (final RESTChangeAction<?> childAction : action.getCreateChildActions()) {
childAction.getFactory().syncDBEntityUpdateChanges(childAction.getDBEntity(), childAction.getRESTEntity(), childAction);
}
for (final RESTChangeAction<?> childAction : action.getUpdateChildActions()) {
final PressGangEntity updateEntity;
if (childAction.getDBEntity() == null) {
updateEntity = getChildEntityForAction((U) entity, (T) dataObject, childAction);
} else {
updateEntity = childAction.getDBEntity();
}
childAction.getFactory().syncDBEntityUpdateChanges(updateEntity, childAction.getRESTEntity(), childAction);
entityCache.addUpdated(childAction.getRESTEntity(), updateEntity);
}
// Sync the additional details now that the child actions have been performed
syncAdditionalDetails((U) entity, (T) dataObject);
}
public abstract void collectChangeInformation(RESTChangeAction<T> parent, T dataObject);
public abstract void syncBaseDetails(U entity, T dataObject);
public void syncAdditionalDetails(U entity, T dataObject) {}
protected void doDeleteChildAction(U entity, T dataObject, RESTChangeAction<?> action) {}
protected PressGangEntity doCreateChildAction(U entity, T dataObject, RESTChangeAction<?> action) {
throw new UnsupportedOperationException("No implementation exists for doCreateChildAction()");
}
protected PressGangEntity getChildEntityForAction(U entity, T dataObject, RESTChangeAction<?> action) {
throw new UnsupportedOperationException("No implementation exists for getChildEntityForAction()");
}
protected <T extends RESTBaseEntityV1<T>,
U extends PressGangEntity,
V extends RESTBaseEntityCollectionV1<T, V, W>,
W extends RESTBaseEntityCollectionItemV1<T, V, W>>
void collectChangeInformationFromCollection(final RESTChangeAction<?> parent, final V collection,
final RESTEntityFactory<T, U, V, W> collectionFactory) {
collectChangeInformationFromCollection(parent, collection, collectionFactory, null);
}
protected <T extends RESTBaseEntityV1<T>,
U extends PressGangEntity,
V extends RESTBaseEntityCollectionV1<T, V, W>,
W extends RESTBaseEntityCollectionItemV1<T, V, W>>
void collectChangeInformationFromCollection(final RESTChangeAction<?> parent, final V collection,
final RESTEntityFactory<T, U, V, W> factory, final String uniqueId) {
collection.removeInvalidChangeItemRequests();
for (final W restEntityItem : collection.getItems()) {
final T restEntity = restEntityItem.getItem();
DatabaseOperation operation = DatabaseOperation.NONE;
if (restEntityItem.returnIsRemoveItem()) {
operation = DatabaseOperation.DELETE;
} else if (restEntityItem.returnIsAddItem()) {
operation = DatabaseOperation.CREATE;
} else if (restEntityItem instanceof RESTUpdateCollectionItemV1
&& ((RESTUpdateCollectionItemV1) restEntityItem).returnIsUpdateItem()) {
operation = DatabaseOperation.UPDATE;
}
// Create the actionable node
final RESTChangeAction<T> childAction = new RESTChangeAction(parent, factory, restEntity, operation);
childAction.setUniqueId(uniqueId);
parent.addChildAction(childAction);
if (operation != DatabaseOperation.DELETE) {
// For deletes there is no need to go any further down the chain, as deleting the parent will delete the children
factory.collectChangeInformation(childAction, restEntity);
}
}
}
protected <T extends RESTBaseEntityV1<T>,
U extends PressGangEntity,
V extends RESTBaseEntityCollectionV1<T, V, W>,
W extends RESTBaseEntityCollectionItemV1<T, V, W>>
void collectChangeInformationFromEntity(final RESTChangeAction<?> parent, final T entity,
final RESTEntityFactory<T, U, V, W> factory) {
collectChangeInformationFromEntity(parent, entity, factory, null);
}
protected <T extends RESTBaseEntityV1<T>,
U extends PressGangEntity,
V extends RESTBaseEntityCollectionV1<T, V, W>,
W extends RESTBaseEntityCollectionItemV1<T, V, W>>
void collectChangeInformationFromEntity(final RESTChangeAction<?> parent, final T entity,
final RESTEntityFactory<T, U, V, W> factory, final String uniqueId) {
final RESTChangeAction<T> childAction = new RESTSingleChangeAction<T>(parent, factory, entity);
childAction.setUniqueId(uniqueId);
parent.addChildAction(childAction);
// Collect the changes for the children, if there are any
if (entity != null) {
factory.collectChangeInformation(childAction, entity);
}
}
/**
* Get the Database class for the factory.
*
* @return The Database class
*/
protected abstract Class<U> getDatabaseClass();
}