package ameba.db.ebean.support;
import ameba.db.ebean.EbeanUtils;
import ameba.db.ebean.internal.ModelInterceptor;
import ameba.exception.UnprocessableEntityException;
import ameba.i18n.Messages;
import ameba.lib.LoggerOwner;
import com.google.common.collect.Collections2;
import com.google.common.collect.Sets;
import io.ebean.*;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import org.apache.commons.lang3.StringUtils;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.internal.util.collection.Ref;
import org.glassfish.jersey.internal.util.collection.Refs;
import javax.inject.Inject;
import javax.persistence.OptimisticLockException;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.*;
import java.net.URI;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* <p>Abstract ModelResourceStructure class.</p>
*
* @author icode
* @since 0.1.6e
*/
public abstract class ModelResourceStructure<URI_ID, MODEL_ID, MODEL> extends LoggerOwner {
protected final SpiEbeanServer server;
protected Class<MODEL> modelType;
protected String defaultFindOrderBy;
@Context
protected UriInfo uriInfo;
@Inject
protected ServiceLocator locator;
private TxScope txScope = null;
private BeanDescriptor descriptor;
/**
* <p>Constructor for ModelResourceStructure.</p>
*
* @param modelType a {@link java.lang.Class} object.
*/
public ModelResourceStructure(Class<MODEL> modelType) {
this(modelType, (SpiEbeanServer) Ebean.getServer(null));
}
/**
* <p>Constructor for ModelResourceStructure.</p>
*
* @param modelType a {@link java.lang.Class} object.
* @param server a {@link io.ebeaninternal.api.SpiEbeanServer} object.
*/
public ModelResourceStructure(Class<MODEL> modelType, SpiEbeanServer server) {
this.modelType = modelType;
this.server = server;
}
/**
* <p>Setter for the field <code>txScope</code>.</p>
*
* @param txScope a {@link io.ebean.TxScope} object.
*/
public void setTxScope(TxScope txScope) {
this.txScope = txScope;
}
/**
* <p>switchToSupportsScope.</p>
*/
public void switchToSupportsScope() {
txScope = TxScope.supports();
}
/**
* <p>switchToNotSupportedScope.</p>
*/
public void switchToNotSupportedScope() {
txScope = TxScope.notSupported();
}
/**
* <p>switchToRequiredScope.</p>
*/
public void switchToRequiredScope() {
txScope = TxScope.required();
}
/**
* <p>switchToRequiredNewScope.</p>
*/
public void switchToRequiredNewScope() {
txScope = TxScope.requiresNew();
}
/**
* <p>switchToMandatoryScope.</p>
*/
public void switchToMandatoryScope() {
txScope = TxScope.mandatory();
}
/**
* <p>getModelBeanDescriptor.</p>
*
* @return a {@link io.ebeaninternal.server.deploy.BeanDescriptor} object.
*/
protected BeanDescriptor getModelBeanDescriptor() {
if (descriptor == null) {
descriptor = server.getBeanDescriptor(modelType);
}
return descriptor;
}
/**
* convert to id
*
* @param id string id
* @return MODEL_ID
* @throws NotFoundException response status 404
*/
@SuppressWarnings("unchecked")
protected final MODEL_ID tryConvertId(Object id) {
try {
return (MODEL_ID) getModelBeanDescriptor().convertId(id);
} catch (Exception e) {
throw new UnprocessableEntityException(Messages.get("info.query.id.unprocessable.entity"), e);
}
}
/**
* convert id to string for insert
*
* @param id id object
* @return id string
*/
protected String idToString(@NotNull MODEL_ID id) {
return id.toString();
}
/**
* <p>setForInsertId.</p>
*
* @param model a MODEL object.
*/
protected void setForInsertId(final MODEL model) {
BeanDescriptor descriptor = getModelBeanDescriptor();
EntityBeanIntercept intercept = ((EntityBean) model)._ebean_getIntercept();
BeanProperty idProp = descriptor.getIdProperty();
if (idProp != null) {
idProp.setValue((EntityBean) model, null);
intercept.setPropertyUnloaded(idProp.getPropertyIndex());
}
}
/**
* <p>flushBatch.</p>
*/
protected void flushBatch() {
Transaction t = server.currentTransaction();
if (t != null)
t.flushBatch();
}
/**
* Insert a model.
* <p>
* success status 201
* </p>
*
* @param model the model to insert
* @return a {@link javax.ws.rs.core.Response} object.
* @throws java.lang.Exception if any.
* @see javax.ws.rs.POST
* @see AbstractModelResource#insert
*/
@SuppressWarnings("unchecked")
public Response insert(@NotNull @Valid final MODEL model) throws Exception {
matchedInsert(model);
setForInsertId(model);
executeTx(t -> {
preInsertModel(model);
insertModel(model);
postInsertModel(model);
});
MODEL_ID id = (MODEL_ID) this.server.getBeanId(model);
return Response.created(buildLocationUri(id)).build();
}
/**
* <p>matchedInsert.</p>
*
* @param model a MODEL object.
* @throws java.lang.Exception if any.
*/
protected void matchedInsert(final MODEL model) throws Exception {
}
/**
* <p>preInsertModel.</p>
*
* @param model a MODEL object.
* @throws java.lang.Exception if any.
*/
protected void preInsertModel(final MODEL model) throws Exception {
}
/**
* <p>insertModel.</p>
*
* @param model a MODEL object.
* @throws java.lang.Exception if any.
*/
protected void insertModel(final MODEL model) throws Exception {
server.insert(model);
}
/**
* <p>postInsertModel.</p>
*
* @param model a MODEL object.
* @throws java.lang.Exception if any.
*/
protected void postInsertModel(final MODEL model) throws Exception {
}
/**
* replace or insert a model.
* <br>
* success replace status 204
* <br>
* fail replace but inserted status 201
*
* @param id the unique id of the model
* @param model the model to update
* @return a {@link javax.ws.rs.core.Response} object.
* @throws java.lang.Exception if any.
* @see javax.ws.rs.PUT
* @see AbstractModelResource#replace
*/
public Response replace(@PathParam("id") final URI_ID id, @NotNull @Valid final MODEL model) throws Exception {
final MODEL_ID mId = tryConvertId(id);
matchedReplace(mId, model);
BeanDescriptor descriptor = getModelBeanDescriptor();
descriptor.convertSetId(mId, (EntityBean) model);
EbeanUtils.forceUpdateAllProperties(server, model);
final Response.ResponseBuilder builder = Response.noContent();
executeTx(t -> {
preReplaceModel(model);
replaceModel(model);
postReplaceModel(model);
}, t -> {
logger().debug("not found model record, insert a model record.");
preInsertModel(model);
insertModel(model);
postInsertModel(model);
builder.status(Response.Status.CREATED).location(buildLocationUri(mId, true));
});
return builder.build();
}
/**
* <p>matchedReplace.</p>
*
* @param id a MODEL_ID object.
* @param model a MODEL object.
* @throws java.lang.Exception if any.
*/
protected void matchedReplace(MODEL_ID id, final MODEL model) throws Exception {
}
/**
* <p>preReplaceModel.</p>
*
* @param model a MODEL object.
* @throws java.lang.Exception if any.
*/
protected void preReplaceModel(final MODEL model) throws Exception {
}
/**
* <p>replaceModel.</p>
*
* @param model a MODEL object.
* @throws java.lang.Exception if any.
*/
protected void replaceModel(final MODEL model) throws Exception {
server.update(model, null, true);
}
/**
* <p>postReplaceModel.</p>
*
* @param model a MODEL object.
* @throws java.lang.Exception if any.
*/
protected void postReplaceModel(final MODEL model) throws Exception {
}
/**
* Update a model items.
* <br>
* success status 204
* <br>
* fail status 422
*
* @param id the unique id of the model
* @param model the model to update
* @return a {@link javax.ws.rs.core.Response} object.
* @throws java.lang.Exception if any.
* @see ameba.core.ws.rs.PATCH
* @see AbstractModelResource#patch
*/
public Response patch(@PathParam("id") final URI_ID id, @NotNull final MODEL model) throws Exception {
MODEL_ID mId = tryConvertId(id);
matchedPatch(mId, model);
BeanDescriptor descriptor = getModelBeanDescriptor();
descriptor.convertSetId(mId, (EntityBean) model);
final Response.ResponseBuilder builder = Response.noContent()
.contentLocation(uriInfo.getAbsolutePath());
return executeTx(t -> {
prePatchModel(model);
patchModel(model);
postPatchModel(model);
return builder.build();
}, t -> {
// id 无法对应数据。实体对象和补丁都正确,但无法处理请求,所以返回422
return builder.status(422).build();
});
}
/**
* <p>matchedPatch.</p>
*
* @param id a MODEL_ID object.
* @param model a MODEL object.
* @throws java.lang.Exception if any.
*/
protected void matchedPatch(final MODEL_ID id, final MODEL model) throws Exception {
}
/**
* <p>prePatchModel.</p>
*
* @param model a MODEL object.
* @throws java.lang.Exception if any.
*/
protected void prePatchModel(final MODEL model) throws Exception {
}
/**
* <p>patchModel.</p>
*
* @param model a MODEL object.
* @throws java.lang.Exception if any.
*/
protected void patchModel(final MODEL model) throws Exception {
server.update(model, null, false);
}
/**
* <p>postPatchModel.</p>
*
* @param model a MODEL object.
* @throws java.lang.Exception if any.
*/
protected void postPatchModel(final MODEL model) throws Exception {
}
/**
* Delete multiple model using Id's from the Matrix.
* <br>
* success status 200
* <br>
* fail status 404
* <br>
* logical delete status 202
*
* @param id The id use for path matching type
* @param ids The ids in the form "/resource/id1" or "/resource/id1;id2;id3"
* @param ids The ids in the form "/resource/id1" or "/resource/id1;id2;id3"
* @param ids The ids in the form "/resource/id1" or "/resource/id1;id2;id3"
* @param ids The ids in the form "/resource/id1" or "/resource/id1;id2;id3"
* @param ids The ids in the form "/resource/id1" or "/resource/id1;id2;id3"
* @param ids The ids in the form "/resource/id1" or "/resource/id1;id2;id3"
* @param permanent a boolean.
* @return a {@link javax.ws.rs.core.Response} object.
* @throws java.lang.Exception if any.
* @see javax.ws.rs.DELETE
* @see AbstractModelResource#deleteMultiple
*/
public Response deleteMultiple(@NotNull @PathParam("ids") URI_ID id,
@NotNull @PathParam("ids") final PathSegment ids,
@QueryParam("permanent") final boolean permanent) throws Exception {
Set<String> idSet = ids.getMatrixParameters().keySet();
final Response.ResponseBuilder builder = Response.noContent();
final TxRunnable failProcess = t -> builder.status(Response.Status.NOT_FOUND);
final MODEL_ID firstId = tryConvertId(ids.getPath());
final Set<MODEL_ID> idCollection = Sets.newLinkedHashSet();
idCollection.add(firstId);
if (!idSet.isEmpty()) {
idCollection.addAll(Collections2.transform(idSet, this::tryConvertId));
}
matchedDelete(firstId, idCollection, permanent);
if (!idSet.isEmpty()) {
executeTx(t -> {
preDeleteMultipleModel(idCollection, permanent);
boolean p = deleteMultipleModel(idCollection, permanent);
if (!p) {
builder.status(Response.Status.ACCEPTED);
}
postDeleteMultipleModel(idCollection, p);
}, failProcess);
} else {
executeTx(t -> {
preDeleteModel(firstId, permanent);
boolean p = deleteModel(firstId, permanent);
if (!p) {
builder.status(Response.Status.ACCEPTED);
}
postDeleteModel(firstId, p);
}, failProcess);
}
return builder.build();
}
/**
* <p>matchedDelete.</p>
*
* @param id a MODEL_ID object.
* @param idSet a {@link java.util.Set} object.
* @param idSet a {@link java.util.Set} object.
* @param permanent a boolean.
* @throws java.lang.Exception if any.
*/
protected void matchedDelete(MODEL_ID id, Set<MODEL_ID> idSet, boolean permanent) throws Exception {
}
/**
* <p>preDeleteMultipleModel.</p>
*
* @param idCollection a {@link java.util.Set} object.
* @param permanent a boolean.
* @throws java.lang.Exception if any.
*/
protected void preDeleteMultipleModel(Set<MODEL_ID> idCollection, boolean permanent) throws Exception {
}
/**
* delete multiple Model
*
* @param idCollection model id collection
* @param permanent a boolean.
* @return if true delete from physical device, if logical delete return false, response status 202
* @throws java.lang.Exception if any.
*/
protected boolean deleteMultipleModel(Set<MODEL_ID> idCollection, boolean permanent) throws Exception {
if (permanent) {
server.deleteAllPermanent(modelType, idCollection);
} else {
server.deleteAll(modelType, idCollection);
}
return permanent;
}
/**
* <p>postDeleteMultipleModel.</p>
*
* @param idCollection a {@link java.util.Set} object.
* @param permanent a boolean.
* @throws java.lang.Exception if any.
*/
protected void postDeleteMultipleModel(Set<MODEL_ID> idCollection, boolean permanent) throws Exception {
}
/**
* <p>preDeleteModel.</p>
*
* @param id a MODEL_ID object.
* @param permanent a boolean.
* @throws java.lang.Exception if any.
*/
protected void preDeleteModel(MODEL_ID id, boolean permanent) throws Exception {
}
/**
* delete a model
*
* @param id model id
* @param permanent a boolean.
* @return if true delete from physical device, if logical delete return false, response status 202
* @throws java.lang.Exception if any.
*/
protected boolean deleteModel(MODEL_ID id, boolean permanent) throws Exception {
if (permanent) {
server.deletePermanent(modelType, id);
} else {
server.delete(modelType, id);
}
return permanent;
}
/**
* <p>postDeleteModel.</p>
*
* @param id a MODEL_ID object.
* @param permanent a boolean.
* @throws java.lang.Exception if any.
*/
protected void postDeleteModel(MODEL_ID id, boolean permanent) throws Exception {
}
/**
* Find a model or model list given its Ids.
*
* @param id The id use for path matching type
* @param ids the id of the model.
* @param ids the id of the model.
* @param ids the id of the model.
* @param ids the id of the model.
* @param ids the id of the model.
* @param ids the id of the model.
* @param includeDeleted a boolean.
* @return a {@link javax.ws.rs.core.Response} object.
* @throws java.lang.Exception if any.
* @see javax.ws.rs.GET
* @see AbstractModelResource#findByIds
*/
public Response findByIds(@NotNull @PathParam("ids") URI_ID id,
@NotNull @PathParam("ids") final PathSegment ids,
@QueryParam("include_deleted") final boolean includeDeleted) throws Exception {
final Query<MODEL> query = server.find(modelType);
final MODEL_ID firstId = tryConvertId(ids.getPath());
Set<String> idSet = ids.getMatrixParameters().keySet();
final Set<MODEL_ID> idCollection = Sets.newLinkedHashSet();
idCollection.add(firstId);
if (!idSet.isEmpty()) {
idCollection.addAll(Collections2.transform(idSet, this::tryConvertId));
}
matchedFindByIds(firstId, idCollection, includeDeleted);
Object model;
if (includeDeleted) {
query.setIncludeSoftDeletes();
}
final TxRunnable configureQuery = t -> {
configDefaultQuery(query);
configFindByIdsQuery(query, includeDeleted);
applyUriQuery(query, false);
};
if (!idSet.isEmpty()) {
model = executeTx(t -> {
configureQuery.run(t);
List<MODEL> m = query.where().idIn(idCollection.toArray()).findList();
return processFoundByIdsModelList(m, includeDeleted);
});
} else {
model = executeTx(t -> {
configureQuery.run(t);
MODEL m = query.setId(firstId).findUnique();
return processFoundByIdModel(m, includeDeleted);
});
}
if (isEmptyEntity(model)) {
throw new NotFoundException();
}
return Response.ok(model).build();
}
/**
* <p>matchedFindByIds.</p>
*
* @param id a MODEL_ID object.
* @param ids a {@link java.util.Set} object.
* @param ids a {@link java.util.Set} object.
* @param includeDeleted a boolean.
* @throws java.lang.Exception if any.
*/
protected void matchedFindByIds(MODEL_ID id, Set<MODEL_ID> ids, boolean includeDeleted) throws Exception {
switchToSupportsScope();
}
/**
* Configure the "Find By Ids" query.
* <p>
* This is only used when no BeanPathProperties where set via UriOptions.
* </p>
* <p>
* This effectively controls the "default" query used to render this model.
* </p>
*
* @param query a {@link io.ebean.Query} object.
* @param includeDeleted a boolean.
* @throws java.lang.Exception if any.
*/
protected void configFindByIdsQuery(final Query<MODEL> query, boolean includeDeleted) throws Exception {
}
/**
* <p>processFoundModel.</p>
*
* @param model a MODEL object.
* @param includeDeleted a boolean.
* @return a {@link java.lang.Object} object.
* @throws java.lang.Exception if any.
*/
protected Object processFoundByIdModel(final MODEL model, boolean includeDeleted) throws Exception {
return model;
}
/**
* <p>processFoundByIdsModelList.</p>
*
* @param models a {@link java.util.List} object.
* @param includeDeleted a boolean.
* @return a {@link java.lang.Object} object.
* @throws java.lang.Exception if any.
*/
protected Object processFoundByIdsModelList(final List<MODEL> models, boolean includeDeleted) throws Exception {
return models;
}
/**
* Find the beans for this beanType.
* <p>
* This can use URL query parameters such as order and maxrows to configure
* the query.
* </p>
*
* @param includeDeleted a boolean.
* @return a {@link javax.ws.rs.core.Response} object.
* @throws java.lang.Exception if any.
* @see javax.ws.rs.GET
* @see AbstractModelResource#find
*/
public Response find(@QueryParam("include_deleted") final boolean includeDeleted) throws Exception {
matchedFind(includeDeleted);
final Query<MODEL> query = server.find(modelType);
if (includeDeleted) {
query.setIncludeSoftDeletes();
}
defaultFindOrderBy(query);
final Ref<FutureRowCount> rowCount = Refs.emptyRef();
Object entity = executeTx(t -> {
configDefaultQuery(query);
configFindQuery(query, includeDeleted);
rowCount.set(applyUriQuery(query));
List<MODEL> list = query.findList();
return processFoundModelList(list, includeDeleted);
});
if (isEmptyEntity(entity)) {
return Response.noContent().build();
}
Response response = Response.ok(entity).build();
applyRowCountHeader(response.getHeaders(), query, rowCount.get());
return response;
}
/**
* <p>matchedFind.</p>
*
* @param includeDeleted a boolean.
* @throws java.lang.Exception if any.
*/
protected void matchedFind(boolean includeDeleted) throws Exception {
switchToSupportsScope();
}
/**
* Configure the "Find" query.
* <p>
* This is only used when no BeanPathProperties where set via UriOptions.
* </p>
* <p>
* This effectively controls the "default" query used with the find all
* query.
* </p>
*
* @param query a {@link io.ebean.Query} object.
* @param includeDeleted a boolean.
* @throws java.lang.Exception if any.
*/
protected void configFindQuery(final Query<MODEL> query, boolean includeDeleted) throws Exception {
}
/**
* <p>processFoundModelList.</p>
*
* @param list a {@link java.util.List} object.
* @param includeDeleted a boolean.
* @return a {@link java.lang.Object} object.
* @throws java.lang.Exception if any.
*/
protected Object processFoundModelList(final List<MODEL> list, boolean includeDeleted) throws Exception {
return list;
}
/**
* <p>defaultFindOrderBy.</p>
*
* @param query a {@link io.ebean.Query} object.
*/
protected void defaultFindOrderBy(Query<MODEL> query) {
if (StringUtils.isNotBlank(defaultFindOrderBy)) {
// see if we should use the default orderBy clause
OrderBy<MODEL> orderBy = query.orderBy();
if (orderBy.isEmpty()) {
query.orderBy(defaultFindOrderBy);
}
}
}
/**
* find model history between start to end timestamp versions
* <p>
* need model mark {@code @History}
*
* @param id model id
* @param start start timestamp
* @param end end timestamp
* @return history versions
* @throws java.lang.Exception any error
*/
public Response fetchHistory(@PathParam("id") URI_ID id,
@PathParam("start") final Timestamp start,
@PathParam("end") final Timestamp end) throws Exception {
final MODEL_ID mId = tryConvertId(id);
matchedFetchHistory(mId, start, end);
final Query<MODEL> query = server.find(modelType);
defaultFindOrderBy(query);
final Ref<FutureRowCount> rowCount = Refs.emptyRef();
Object entity = executeTx(t -> {
configDefaultQuery(query);
configFetchHistoryQuery(query, mId, start, end);
applyUriQuery(query, false);
applyPageConfig(query);
List<Version<MODEL>> list = query.findVersionsBetween(start, end);
rowCount.set(fetchRowCount(query));
return processFetchedHistoryModelList(list, mId, start, end);
});
if (isEmptyEntity(entity)) {
return Response.noContent().build();
}
Response response = Response.ok(entity).build();
applyRowCountHeader(response.getHeaders(), query, rowCount.get());
return response;
}
/**
* <p>matchedFetchHistory.</p>
*
* @param id a MODEL_ID object.
* @param start a {@link java.sql.Timestamp} object.
* @param end a {@link java.sql.Timestamp} object.
* @throws java.lang.Exception if any.
*/
protected void matchedFetchHistory(final MODEL_ID id,
final Timestamp start,
final Timestamp end) throws Exception {
switchToSupportsScope();
}
/**
* <p>configFetchHistoryQuery.</p>
*
* @param query a {@link io.ebean.Query} object.
* @param id a MODEL_ID object.
* @param start a {@link java.sql.Timestamp} object.
* @param end a {@link java.sql.Timestamp} object.
* @throws java.lang.Exception if any.
*/
protected void configFetchHistoryQuery(final Query<MODEL> query,
final MODEL_ID id,
final Timestamp start,
final Timestamp end) throws Exception {
}
/**
* <p>processFetchedHistoryModelList.</p>
*
* @param list a {@link java.util.List} object.
* @param id a MODEL_ID object.
* @param start a {@link java.sql.Timestamp} object.
* @param end a {@link java.sql.Timestamp} object.
* @return a {@link java.lang.Object} object.
* @throws java.lang.Exception if any.
*/
protected Object processFetchedHistoryModelList(final List<Version<MODEL>> list,
final MODEL_ID id,
final Timestamp start,
final Timestamp end) throws Exception {
return list;
}
/**
* <p>fetchHistory.</p>
*
* @param id a URI_ID object.
* @return a {@link javax.ws.rs.core.Response} object.
* @throws java.lang.Exception if any.
*/
public Response fetchHistory(@PathParam("id") URI_ID id) throws Exception {
final MODEL_ID mId = tryConvertId(id);
matchedFetchHistory(mId);
final Query<MODEL> query = server.find(modelType);
defaultFindOrderBy(query);
final Ref<FutureRowCount> rowCount = Refs.emptyRef();
Object entity = executeTx(t -> {
configDefaultQuery(query);
configFetchHistoryQuery(query, mId);
applyUriQuery(query, false);
applyPageConfig(query);
List<Version<MODEL>> list = query.findVersions();
rowCount.set(fetchRowCount(query));
return processFetchedHistoryModelList(list, mId);
});
if (isEmptyEntity(entity)) {
return Response.noContent().build();
}
Response response = Response.ok(entity).build();
applyRowCountHeader(response.getHeaders(), query, rowCount.get());
return response;
}
/**
* <p>matchedFetchHistory.</p>
*
* @param id a MODEL_ID object.
* @throws java.lang.Exception if any.
*/
protected void matchedFetchHistory(final MODEL_ID id) throws Exception {
switchToSupportsScope();
}
/**
* <p>configFetchHistoryQuery.</p>
*
* @param query a {@link io.ebean.Query} object.
* @param id a MODEL_ID object.
* @throws java.lang.Exception if any.
*/
protected void configFetchHistoryQuery(final Query<MODEL> query,
final MODEL_ID id) throws Exception {
}
/**
* <p>processFetchedHistoryModelList.</p>
*
* @param list a {@link java.util.List} object.
* @param id a MODEL_ID object.
* @return a {@link java.lang.Object} object.
* @throws java.lang.Exception if any.
*/
protected Object processFetchedHistoryModelList(final List<Version<MODEL>> list,
final MODEL_ID id) throws Exception {
return list;
}
/**
* find history as of timestamp
*
* @param id model id
* @param asOf Timestamp
* @return history model
* @throws java.lang.Exception any error
*/
public Response fetchHistoryAsOf(@PathParam("id") URI_ID id,
@PathParam("asOf") final Timestamp asOf) throws Exception {
final MODEL_ID mId = tryConvertId(id);
matchedFetchHistoryAsOf(mId, asOf);
final Query<MODEL> query = server.find(modelType);
defaultFindOrderBy(query);
Object entity = executeTx(t -> {
configDefaultQuery(query);
configFetchHistoryAsOfQuery(query, mId, asOf);
applyUriQuery(query, false);
MODEL model = query.asOf(asOf).setId(mId).findUnique();
return processFetchedHistoryAsOfModel(mId, model, asOf);
});
if (isEmptyEntity(entity)) {
return Response.noContent().build();
}
return Response.ok(entity).build();
}
/**
* <p>matchedFetchHistoryAsOf.</p>
*
* @param id a MODEL_ID object.
* @param asOf a {@link java.sql.Timestamp} object.
* @throws java.lang.Exception if any.
*/
protected void matchedFetchHistoryAsOf(final MODEL_ID id, final Timestamp asOf) throws Exception {
switchToSupportsScope();
}
/**
* <p>configFetchHistoryAsOfQuery.</p>
*
* @param query a {@link io.ebean.Query} object.
* @param id a MODEL_ID object.
* @param asOf a {@link java.sql.Timestamp} object.
* @throws java.lang.Exception if any.
*/
protected void configFetchHistoryAsOfQuery(final Query<MODEL> query,
final MODEL_ID id,
final Timestamp asOf) throws Exception {
}
/**
* <p>processFetchedHistoryAsOfModel.</p>
*
* @param id a MODEL_ID object.
* @param model a MODEL object.
* @param asOf a {@link java.sql.Timestamp} object.
* @return a {@link java.lang.Object} object.
* @throws java.lang.Exception if any.
*/
protected Object processFetchedHistoryAsOfModel(final MODEL_ID id,
final MODEL model,
final Timestamp asOf) throws Exception {
return model;
}
/**
* all query config default query
*
* @param query query
* @throws java.lang.Exception if any.
*/
protected void configDefaultQuery(final Query<MODEL> query) throws Exception {
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////// ///////////////////////////////////
/////////////////////////////////// useful method block ///////////////////////////////////
/////////////////////////////////// ///////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* <p>isEmptyEntity.</p>
*
* @param entity a {@link java.lang.Object} object.
* @return a boolean.
*/
protected boolean isEmptyEntity(Object entity) {
return entity == null || (entity instanceof Collection && ((Collection) entity).isEmpty());
}
/**
* apply uri query parameter on query
*
* @param query Query
* @param needPageList need page list
* @return page list count or null
* @see ModelInterceptor#applyUriQuery(MultivaluedMap, SpiQuery, ServiceLocator, boolean)
*/
protected FutureRowCount applyUriQuery(final Query<MODEL> query, boolean needPageList) {
return ModelInterceptor.applyUriQuery(uriInfo.getQueryParameters(), (SpiQuery) query, locator, needPageList);
}
/**
* <p>applyUriQuery.</p>
*
* @param query a {@link io.ebean.Query} object.
* @return a {@link io.ebean.FutureRowCount} object.
*/
protected FutureRowCount applyUriQuery(final Query<MODEL> query) {
return applyUriQuery(query, true);
}
/**
* <p>applyPageConfig.</p>
*
* @param query a {@link io.ebean.Query} object.
*/
protected void applyPageConfig(Query query) {
ModelInterceptor.applyPageConfig(uriInfo.getQueryParameters(), query);
}
/**
* <p>fetchRowCount.</p>
*
* @param query a {@link io.ebean.Query} object.
* @return a {@link io.ebean.FutureRowCount} object.
*/
protected FutureRowCount fetchRowCount(Query query) {
return ModelInterceptor.fetchRowCount(uriInfo.getQueryParameters(), query);
}
/**
* <p>applyRowCountHeader.</p>
*
* @param headerParams a {@link javax.ws.rs.core.MultivaluedMap} object.
* @param query a {@link io.ebean.Query} object.
* @param rowCount a {@link io.ebean.FutureRowCount} object.
*/
protected void applyRowCountHeader(MultivaluedMap<String, Object> headerParams, Query query, FutureRowCount rowCount) {
ModelInterceptor.applyRowCountHeader(headerParams, query, rowCount);
}
/**
* <p>processTransactionError.</p>
*
* @param t Transaction
* @param callable a {@link ameba.db.ebean.support.ModelResourceStructure.TxCallable} object.
* @param process a {@link ameba.db.ebean.support.ModelResourceStructure.TxCallable} object.
* @param <T> model
* @return model
* @throws java.lang.Exception if any.
*/
protected <T> T processTransactionError(Transaction t, TxCallable<T> callable, TxCallable<T> process) throws Exception {
try {
return callable.call(t);
} catch (Exception e) {
return processCheckRowCountError(t, e, e, process);
}
}
/**
* <p>processCheckRowCountError.</p>
*
* @param t Transaction
* @param root root exception
* @param e exception
* @param process process method
* @param <T> model
* @return model
* @throws java.lang.Exception if any.
*/
protected <T> T processCheckRowCountError(Transaction t, Exception root, Throwable e, TxCallable<T> process) throws Exception {
if (e == null) {
throw root;
}
if (e instanceof OptimisticLockException) {
if ("checkRowCount".equals(e.getStackTrace()[0].getMethodName())) {
if (process != null)
return process.call(t);
}
}
return processCheckRowCountError(t, root, e.getCause(), process);
}
/**
* <p>executeTx.</p>
*
* @param r a {@link ameba.db.ebean.support.ModelResourceStructure.TxRunnable} object.
* @param errorHandler error handler
* @throws java.lang.Exception if any.
*/
protected void executeTx(final TxRunnable r, final TxRunnable errorHandler) throws Exception {
Transaction transaction = beginTransaction();
configureTransDefault(transaction);
processTransactionError(transaction, t -> {
try {
r.run(t);
t.commit();
} catch (Throwable e) {
t.rollback(e);
throw e;
} finally {
t.end();
}
return null;
}, errorHandler != null ? (TxCallable) t -> {
errorHandler.run(t);
return null;
} : null);
}
/**
* <p>beginTransaction.</p>
*
* @return a {@link io.ebean.Transaction} object.
*/
protected Transaction beginTransaction() {
return server.beginTransaction(txScope);
}
/**
* <p>executeTx.</p>
*
* @param r a {@link ameba.db.ebean.support.ModelResourceStructure.TxRunnable} object.
* @throws java.lang.Exception if any.
*/
protected void executeTx(TxRunnable r) throws Exception {
executeTx(r, null);
}
/**
* <p>executeTx.</p>
*
* @param c a {@link ameba.db.ebean.support.ModelResourceStructure.TxCallable} object.
* @param <O> a O object.
* @return a O object.
* @throws java.lang.Exception if any.
*/
protected <O> O executeTx(TxCallable<O> c) throws Exception {
return executeTx(c, null);
}
/**
* <p>executeTx.</p>
*
* @param c a {@link ameba.db.ebean.support.ModelResourceStructure.TxCallable} object.
* @param errorHandler a {@link ameba.db.ebean.support.ModelResourceStructure.TxCallable} object.
* @param <O> a O object.
* @return a O object.
* @throws java.lang.Exception if any.
*/
@SuppressWarnings("unchecked")
protected <O> O executeTx(final TxCallable<O> c, final TxCallable<O> errorHandler) throws Exception {
Transaction transaction = beginTransaction();
configureTransDefault(transaction);
return processTransactionError(transaction, t -> {
Object o = null;
try {
o = c.call(t);
t.commit();
} catch (Throwable e) {
t.rollback(e);
throw e;
} finally {
t.end();
}
return (O) o;
}, errorHandler);
}
/**
* <p>configureTransDefault.</p>
*
* @param transaction a {@link io.ebean.Transaction} object.
*/
protected void configureTransDefault(Transaction transaction) {
}
/**
* <p>buildLocationUri.</p>
*
* @param id a MODEL_ID object.
* @param useTemplate use current template
* @return a {@link java.net.URI} object.
*/
protected URI buildLocationUri(MODEL_ID id, boolean useTemplate) {
if (id == null) {
throw new NotFoundException();
}
UriBuilder ub = uriInfo.getAbsolutePathBuilder();
if (useTemplate) {
return ub.build(id);
} else {
return ub.path(idToString(id)).build();
}
}
/**
* <p>buildLocationUri.</p>
*
* @param id a MODEL_ID object.
* @return a {@link java.net.URI} object.
*/
protected URI buildLocationUri(MODEL_ID id) {
return buildLocationUri(id, false);
}
protected interface TxRunnable {
void run(Transaction transaction) throws Exception;
}
protected interface TxCallable<O> {
O call(Transaction transaction) throws Exception;
}
}