package ameba.db.ebean;
import ameba.core.Application;
import ameba.core.Requests;
import ameba.db.ebean.filter.Filter;
import ameba.db.ebean.internal.ListExpressionValidation;
import ameba.db.ebean.jackson.CommonBeanSerializer;
import ameba.exception.UnprocessableEntityException;
import ameba.i18n.Messages;
import ameba.message.filtering.EntityFieldsUtils;
import ameba.message.internal.BeanPathProperties;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import io.ebean.FetchPath;
import io.ebean.OrderBy;
import io.ebean.Query;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionList;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import org.apache.commons.lang3.StringUtils;
import org.glassfish.hk2.api.ServiceLocator;
import javax.ws.rs.container.ResourceInfo;
import java.util.*;
import static io.ebean.OrderBy.Property;
/**
* <p>EbeanUtils class.</p>
*
* @author icode
* @since 0.1.6e
*
*/
public class EbeanUtils {
/**
* Constant <code>PATH_PROPS_PARSED="EbeanUtils.class + .BeanPathProperties"</code>
*/
public static final String PATH_PROPS_PARSED = EbeanUtils.class + ".BeanPathProperties";
private EbeanUtils() {
}
/**
* <p>forceUpdateAllProperties.</p>
*
* @param server a {@link io.ebeaninternal.api.SpiEbeanServer} object.
* @param model a T object.
* @param <T> a T object.
*/
@SuppressWarnings("unchecked")
public static <T> void forceUpdateAllProperties(SpiEbeanServer server, T model) {
forceUpdateAllProperties(server.getBeanDescriptor((Class<T>) model.getClass()), model);
}
/**
* <p>forceUpdateAllProperties.</p>
*
* @param beanDescriptor a {@link io.ebeaninternal.server.deploy.BeanDescriptor} object.
* @param model a T object.
* @param <T> a T object.
*/
public static <T> void forceUpdateAllProperties(BeanDescriptor<T> beanDescriptor, T model) {
EntityBeanIntercept intercept = ((EntityBean) model)._ebean_getIntercept();
intercept.setLoaded();
int idIndex = beanDescriptor.getIdProperty().getPropertyIndex();
for (int i = 0; i < intercept.getPropertyLength(); i++) {
if (i != idIndex) {
intercept.markPropertyAsChanged(i);
intercept.setLoadedProperty(i);
}
}
}
/**
* parse uri query param to BeanPathProperties for Ebean.json().toJson()
*
* @return BeanPathProperties
* @see CommonBeanSerializer#serialize(Object, JsonGenerator, SerializerProvider)
*/
public static FetchPath getRequestFetchPath() {
Object properties = Requests.getProperty(PATH_PROPS_PARSED);
if (properties == null) {
BeanPathProperties pathProperties = EntityFieldsUtils.parsePathProperties();
if (pathProperties == null) {
Requests.setProperty(PATH_PROPS_PARSED, false);
} else {
properties = EbeanPathProps.of(pathProperties);
Requests.setProperty(PATH_PROPS_PARSED, properties);
}
} else if (properties.equals(false)) {
return null;
}
return (FetchPath) properties;
}
/**
* <p>appendOrder.</p>
*
* @param orderBy a {@link io.ebean.OrderBy} object.
* @param orderByClause a {@link java.lang.String} object.
* @param <T> a T object.
*/
public static <T> void appendOrder(OrderBy<T> orderBy, String orderByClause) {
if (orderByClause == null) {
return;
}
String[] chunks = orderByClause.split(",");
for (String chunk : chunks) {
String[] pairs = chunk.split(" ");
Property p = parseOrderProperty(pairs);
if (p != null) {
orderBy.add(p);
}
}
}
/**
* <p>checkQuery.</p>
*
* @param query a {@link io.ebean.Query} object.
* @param locator a {@link org.glassfish.hk2.api.ServiceLocator} object.
*/
public static void checkQuery(Query<?> query, ServiceLocator locator) {
checkQuery(query, null, null, locator);
}
/**
* <p>checkQuery.</p>
*
* @param query a {@link io.ebean.Query} object.
* @param whitelist a {@link java.util.Set} object.
* @param blacklist a {@link java.util.Set} object.
* @param locator a {@link org.glassfish.hk2.api.ServiceLocator} object.
*/
public static void checkQuery(Query<?> query, Set<String> whitelist,
Set<String> blacklist, ServiceLocator locator) {
ResourceInfo resource = locator.getService(ResourceInfo.class);
Class<?> rc = resource.getResourceClass();
Set<String> wl = null, bl = null;
if (rc != null) {
Filter filter = rc.getAnnotation(Filter.class);
if (filter != null) {
if (filter.whitelist().length > 0) {
wl = Sets.newLinkedHashSet();
Collections.addAll(wl, filter.whitelist());
}
if (filter.blacklist().length > 0) {
bl = Sets.newLinkedHashSet();
Collections.addAll(bl, filter.blacklist());
}
}
}
if (whitelist != null) {
if (wl == null) {
wl = Sets.newLinkedHashSet();
}
wl.addAll(whitelist);
}
if (blacklist != null) {
if (bl == null) {
bl = Sets.newLinkedHashSet();
}
bl.addAll(blacklist);
}
checkQuery((SpiQuery) query, wl, bl, locator.getService(Application.Mode.class).isProd());
}
/**
* <p>checkQuery.</p>
*
* @param query a {@link io.ebeaninternal.api.SpiQuery} object.
* @param whitelist a {@link java.util.Set} object.
* @param blacklist a {@link java.util.Set} object.
* @param ignoreUnknown a boolean.
*/
public static void checkQuery(SpiQuery<?> query, Set<String> whitelist,
Set<String> blacklist, boolean ignoreUnknown) {
checkQuery(
query,
new ListExpressionValidation(
query.getBeanDescriptor(), whitelist, blacklist
),
ignoreUnknown
);
}
/**
* <p>checkQuery.</p>
*
* @param query a {@link io.ebeaninternal.api.SpiQuery} object.
* @param validation a {@link ameba.db.ebean.internal.ListExpressionValidation} object.
* @param ignoreUnknown a boolean.
*/
public static void checkQuery(SpiQuery<?> query, ListExpressionValidation validation, boolean ignoreUnknown) {
if (query != null) {
validate(query.getWhereExpressions(), validation, ignoreUnknown);
validate(query.getHavingExpressions(), validation, ignoreUnknown);
validate(query.getOrderBy(), validation, ignoreUnknown);
Set<String> invalid = validation.getUnknownProperties();
if (!ignoreUnknown && !invalid.isEmpty()) {
UnprocessableEntityException.throwQuery(invalid);
}
}
}
/**
* <p>validate.</p>
*
* @param expressions a {@link io.ebeaninternal.api.SpiExpressionList} object.
* @param validation a {@link ameba.db.ebean.internal.ListExpressionValidation} object.
* @param ignoreUnknown a boolean.
*/
public static void validate(SpiExpressionList<?> expressions,
ListExpressionValidation validation,
boolean ignoreUnknown) {
if (expressions == null) return;
List<SpiExpression> list = expressions.getUnderlyingList();
Iterator<SpiExpression> it = list.iterator();
while (it.hasNext()) {
it.next().validate(validation);
if (ignoreUnknown && !validation.lastValid()) {
it.remove();
}
}
}
/**
* <p>validate.</p>
*
* @param orderBy a {@link io.ebean.OrderBy} object.
* @param validation a {@link ameba.db.ebean.internal.ListExpressionValidation} object.
* @param ignoreUnknown a boolean.
*/
public static void validate(OrderBy<?> orderBy,
ListExpressionValidation validation,
boolean ignoreUnknown) {
if (orderBy == null) return;
Iterator<Property> it = orderBy.getProperties().iterator();
while (it.hasNext()) {
validation.validate(it.next().getProperty());
if (ignoreUnknown && !validation.lastValid()) {
it.remove();
}
}
}
private static Property parseOrderProperty(String[] pairs) {
if (pairs.length == 0) {
return null;
}
ArrayList<String> wordList = Lists.newArrayListWithCapacity(pairs.length);
for (String pair : pairs) {
if (StringUtils.isNotBlank(pair)) {
wordList.add(pair);
}
}
if (wordList.isEmpty()) {
return null;
}
String field = wordList.get(0);
if (wordList.size() == 1) {
if (field.startsWith("-")) {
return new Property(field.substring(1), false);
} else {
return new Property(field, true);
}
}
if (wordList.size() == 2) {
boolean asc = isOrderAscending(wordList.get(1));
return new Property(field, asc);
}
throw new UnprocessableEntityException(
Messages.get("info.query.orderby1.unprocessable.entity",
Arrays.toString(pairs), wordList.size())
);
}
private static boolean isOrderAscending(String s) {
s = s.toLowerCase();
if (s.startsWith("asc")) {
return true;
}
if (s.startsWith("desc")) {
return false;
}
throw new UnprocessableEntityException(Messages.get("info.query.orderby0.unprocessable.entity", s));
}
}