package com.kth.baasio.query;
import com.kth.baasio.Baas;
import com.kth.baasio.callback.BaasioQueryAsyncTask;
import com.kth.baasio.callback.BaasioQueryCallback;
import com.kth.baasio.entity.BaasioBaseEntity;
import com.kth.baasio.entity.group.BaasioGroup;
import com.kth.baasio.entity.user.BaasioUser;
import com.kth.baasio.exception.BaasioError;
import com.kth.baasio.exception.BaasioException;
import com.kth.baasio.response.BaasioResponse;
import com.kth.baasio.utils.ObjectUtils;
import org.springframework.http.HttpMethod;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Stack;
public class BaasioQuery implements Cloneable {
public enum ORDER_BY {
ASCENDING, DESCENDING
};
private Stack<String> cursorStack = new Stack<String>();
private String currentCursor;
private int limit = 10;
private BaasioBaseEntity entity;
private BaasioBaseEntity relatedWithThisEntity;
private String relationship;
private BaasioGroup inThisGroup;
private String projectionIn;
private String wheres;
private String orderByKey;
private ORDER_BY orderByMethod = ORDER_BY.ASCENDING;
private String rawString;
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
public BaasioQuery() {
}
/**
* Get raw query string.
*
* @return the rawString
*/
public String getRawString() {
return rawString;
}
/**
* Set raw query string. If set raw query string, other query setting will
* reset.
*
* @param rawString the rawString to set
*/
public void setRawString(String rawString) {
this.entity = null;
this.inThisGroup = null;
this.relatedWithThisEntity = null;
this.relationship = null;
this.rawString = rawString;
}
/**
* Set limit.
*
* @param limit Limit
*/
public void setLimit(int limit) {
this.limit = limit;
}
/**
* Get limit.
*
* @return Limit
*/
public int getLimit() {
return limit;
}
/**
* Set query method by entity type.
*
* @param entity Entity. Entity must have type.
*/
public <T extends BaasioBaseEntity> void setType(T entity) {
if (ObjectUtils.isEmpty(entity.getType())) {
throw new IllegalArgumentException(BaasioError.ERROR_MISSING_TYPE);
}
this.inThisGroup = null;
this.relatedWithThisEntity = null;
this.relationship = null;
this.entity = new BaasioBaseEntity(entity.getType());
}
/**
* Set entity type to query.
*
* @param type Entity type
*/
public void setType(String type) {
this.inThisGroup = null;
this.relatedWithThisEntity = null;
this.relationship = null;
this.entity = new BaasioBaseEntity(type);
}
/**
* Get entity type to query.
*
* @return Entity type to query
*/
public String getType() {
return entity.getType();
}
/**
* Set query method by relationship. If set relation, type and group will
* reset.
*
* @param entity Target entity. Entity must have type and uuid.
* @param relationship Relationship name
*/
public <T extends BaasioBaseEntity> void setRelation(T entity, String relationship) {
if (ObjectUtils.isEmpty(entity)) {
throw new IllegalArgumentException(BaasioError.ERROR_MISSING_TARGET_ENTITY);
}
if (ObjectUtils.isEmpty(entity.getType())) {
throw new IllegalArgumentException(BaasioError.ERROR_MISSING_TYPE);
}
if (ObjectUtils.isEmpty(relationship)) {
throw new IllegalArgumentException(BaasioError.ERROR_MISSING_RELATIONSHIP);
}
if (entity instanceof BaasioUser) {
BaasioUser targetUserEntity = (BaasioUser)entity;
if (ObjectUtils.isEmpty(targetUserEntity.getUniqueKey())) {
throw new IllegalArgumentException(BaasioError.ERROR_MISSING_USER_UUID_OR_USERNAME);
}
} else {
if (ObjectUtils.isEmpty(entity.getUniqueKey())) {
throw new IllegalArgumentException(BaasioError.ERROR_MISSING_UUID_OR_NAME);
}
}
if (ObjectUtils.isEmpty(relationship)) {
throw new IllegalArgumentException(BaasioError.ERROR_MISSING_RELATIONSHIP);
}
this.entity = null;
this.inThisGroup = null;
this.relatedWithThisEntity = entity;
this.relationship = relationship;
}
/**
* Get relation name to query.
*
* @return Relationship name
*/
public String getRelationShip() {
return this.relationship;
}
/**
* Get target entity to query which have relationship.
*
* @return Target entity
*/
public BaasioBaseEntity getRelationEntity() {
return this.relatedWithThisEntity;
}
/**
* Set query method by group. If set group, type and group will reset.
*
* @param group Target group. Group must have group path.
*/
public void setGroup(BaasioGroup group) {
if (ObjectUtils.isEmpty(group)) {
throw new IllegalArgumentException(BaasioError.ERROR_MISSING_TARGET_GROUP_ENTITY);
}
if (ObjectUtils.isEmpty(group.getUniqueKey())) {
throw new IllegalArgumentException(BaasioError.ERROR_MISSING_GROUP_UUID_OR_PATH);
}
this.entity = null;
this.relatedWithThisEntity = null;
this.relationship = null;
this.inThisGroup = group;
}
/**
* Get target group to query.
*
* @return Target group
*/
public BaasioGroup getGroup() {
return this.inThisGroup;
}
/**
* Get columns to query. A list of which columns to return.
*
* @return Projection clause
*/
public String getProjectionIn() {
return projectionIn;
}
/**
* Set columns to query. A list of which columns to return.
*
* @param projectionIn A list of which columns to return
*/
public void setProjectionIn(String projectionIn) {
this.projectionIn = projectionIn;
}
/**
* Get where clause.
*
* @return the wheres
*/
public String getWheres() {
return wheres;
}
/**
* Set where clause.
*
* @param wheres Where clause
*/
public void setWheres(String wheres) {
this.wheres = wheres;
}
/**
* Get next cursor to query.
*
* @return Next cursor
*/
private String getNextCursor() {
int index = -1;
if (!ObjectUtils.isEmpty(currentCursor)) {
index = cursorStack.indexOf(currentCursor);
}
if (cursorStack.size() > index + 1) {
String next = cursorStack.get(index + 1);
if (!ObjectUtils.isEmpty(next)) {
return next;
}
}
return null;
}
/**
* To check whether have next entity.
*
* @return If true, have next entity.
*/
public boolean hasNextEntities() {
int index = -1;
if (!ObjectUtils.isEmpty(currentCursor)) {
index = cursorStack.indexOf(currentCursor);
}
if (cursorStack.size() > index + 1) {
String next = cursorStack.get(index + 1);
if (!ObjectUtils.isEmpty(next)) {
return true;
}
}
return false;
}
/**
* Get previous cursor to query.
*
* @return Previous cursor
*/
private String getPrevCursor() {
int index = -1;
if (!ObjectUtils.isEmpty(currentCursor)) {
index = cursorStack.indexOf(currentCursor);
}
if (index > 0) {
String prev = cursorStack.get(index - 1);
if (!ObjectUtils.isEmpty(prev)) {
return prev;
}
}
return null;
}
/**
* To check whether have previous entity.
*
* @return If true, have previous entity.
*/
public boolean hasPrevEntities() {
int index = -1;
if (!ObjectUtils.isEmpty(currentCursor)) {
index = cursorStack.indexOf(currentCursor);
}
if (index > -1) {
if (index == 0) {
return true;
} else {
String prev = cursorStack.get(index - 1);
if (!ObjectUtils.isEmpty(prev)) {
return true;
}
}
}
return false;
}
/**
* Set orderby clause.
*
* @param key Property name
* @param orderBy Orderby method
* @return this
*/
public BaasioQuery setOrderBy(String key, ORDER_BY orderBy) {
orderByKey = key;
orderByMethod = orderBy;
return this;
}
private StringBuilder getQueryBaseString() throws BaasioException {
StringBuilder queryStringBuilder = new StringBuilder();
queryStringBuilder.append("select ");
if (!ObjectUtils.isEmpty(projectionIn)) {
queryStringBuilder.append(getProjectionIn());
} else {
queryStringBuilder.append("*");
}
if (!ObjectUtils.isEmpty(getWheres())) {
queryStringBuilder.append(" where ");
queryStringBuilder.append(getWheres());
}
if (!ObjectUtils.isEmpty(orderByKey)) {
queryStringBuilder.append(" order by ");
queryStringBuilder.append(orderByKey);
if (orderByMethod == ORDER_BY.ASCENDING) {
queryStringBuilder.append(" asc");
} else {
queryStringBuilder.append(" desc");
}
}
StringBuilder result = new StringBuilder();
if (queryStringBuilder.length() > 0) {
if (result.length() <= 0) {
result.append("?ql=");
}
try {
result.append(URLEncoder.encode(queryStringBuilder.toString(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new BaasioException(e);
}
}
if (getLimit() != 10) {
result.append("&limit=" + getLimit());
}
return result;
}
private String getQueryString(boolean next) throws BaasioException {
StringBuilder queryStringBuilder = getQueryBaseString();
if (getLimit() != 10) {
queryStringBuilder.append("&limit=" + getLimit());
}
if (next) {
if (hasNextEntities()) {
queryStringBuilder.append("&cursor=" + getNextCursor());
} else {
throw new BaasioException(BaasioError.ERROR_QUERY_NO_MORE_NEXT);
}
} else {
if (hasPrevEntities()) {
queryStringBuilder.append("&cursor=" + getPrevCursor());
} else {
throw new BaasioException(BaasioError.ERROR_QUERY_NO_MORE_PREV);
}
}
return queryStringBuilder.toString();
}
/**
* Query entities.
*
* @return Query result
*/
public BaasioResponse query() throws BaasioException {
BaasioResponse response = null;
if (ObjectUtils.isEmpty(getRawString())) {
if (!ObjectUtils.isEmpty(relatedWithThisEntity)) {
if (ObjectUtils.isEmpty(relatedWithThisEntity.getType())) {
throw new IllegalArgumentException(BaasioError.ERROR_MISSING_TYPE);
}
response = Baas.io().apiRequest(HttpMethod.GET, null, null,
relatedWithThisEntity.getType(), relatedWithThisEntity.getUniqueKey(),
relationship + getQueryBaseString());
} else if (!ObjectUtils.isEmpty(inThisGroup)) {
response = Baas.io().apiRequest(HttpMethod.GET, null, null, "groups",
inThisGroup.getUniqueKey(), "users" + getQueryBaseString());
} else {
if (ObjectUtils.isEmpty(getType())) {
throw new IllegalArgumentException(BaasioError.ERROR_MISSING_TYPE);
}
response = Baas.io().apiRequest(HttpMethod.GET, null, null,
getType() + getQueryBaseString());
}
} else {
response = Baas.io().apiRequest(HttpMethod.GET, null, null, getRawString());
}
if (!ObjectUtils.isEmpty(response)) {
currentCursor = null;
cursorStack.clear();
String nextCursor = response.getCursor();
if (!ObjectUtils.isEmpty(nextCursor)) {
cursorStack.push(nextCursor);
}
return response;
} else {
throw new BaasioException(BaasioError.ERROR_UNKNOWN_NO_RESPONSE_DATA);
}
}
/**
* Query entities. Executes asynchronously in background and the callbacks
* are called in the UI thread.
*
* @param callback Result callback
*/
public void queryInBackground(final BaasioQueryCallback callback) {
(new BaasioQueryAsyncTask(this, callback) {
@Override
public BaasioResponse doTask() throws Exception {
return query();
}
}).execute();
}
/**
* Query previous entities.
*
* @return Query result
*/
public BaasioResponse prev() throws BaasioException {
BaasioResponse response = null;
if (ObjectUtils.isEmpty(getRawString())) {
if (!ObjectUtils.isEmpty(relatedWithThisEntity)) {
if (ObjectUtils.isEmpty(relatedWithThisEntity.getType())) {
throw new IllegalArgumentException(BaasioError.ERROR_MISSING_TYPE);
}
response = Baas.io().apiRequest(HttpMethod.GET, null, null,
relatedWithThisEntity.getType(), relatedWithThisEntity.getUniqueKey(),
relationship + getQueryString(false));
} else if (!ObjectUtils.isEmpty(inThisGroup)) {
response = Baas.io().apiRequest(HttpMethod.GET, null, null, "groups",
inThisGroup.getPath(), "users" + getQueryString(false));
} else {
if (ObjectUtils.isEmpty(getType())) {
throw new IllegalArgumentException(BaasioError.ERROR_MISSING_TYPE);
}
response = Baas.io().apiRequest(HttpMethod.GET, null, null,
getType() + getQueryString(false));
}
} else {
if (hasPrevEntities()) {
response = Baas.io().apiRequest(HttpMethod.GET, null, null,
getRawString() + "&cursor=" + getPrevCursor());
} else {
throw new BaasioException(BaasioError.ERROR_QUERY_NO_MORE_PREV);
}
}
if (!ObjectUtils.isEmpty(response)) {
currentCursor = getPrevCursor();
do {
cursorStack.pop();
} while (!cursorStack.isEmpty() && !cursorStack.lastElement().equals(currentCursor));
String nextCursor = response.getCursor();
if (!ObjectUtils.isEmpty(nextCursor)) {
cursorStack.push(nextCursor);
}
}
return response;
}
/**
* Query previous entities. Executes asynchronously in background and the
* callbacks are called in the UI thread.
*
* @param callback Result callback
*/
public void prevInBackground(final BaasioQueryCallback callback) {
(new BaasioQueryAsyncTask(this, callback) {
@Override
public BaasioResponse doTask() throws Exception {
return prev();
}
}).execute();
}
/**
* Query next entities.
*
* @return Query result
*/
public BaasioResponse next() throws BaasioException {
BaasioResponse response = null;
if (ObjectUtils.isEmpty(getRawString())) {
if (!ObjectUtils.isEmpty(relatedWithThisEntity)) {
if (ObjectUtils.isEmpty(relatedWithThisEntity.getType())) {
throw new IllegalArgumentException(BaasioError.ERROR_MISSING_TYPE);
}
response = Baas.io().apiRequest(HttpMethod.GET, null, null,
relatedWithThisEntity.getType(), relatedWithThisEntity.getUniqueKey(),
relationship + getQueryString(true));
} else if (!ObjectUtils.isEmpty(inThisGroup)) {
response = Baas.io().apiRequest(HttpMethod.GET, null, null, "groups",
inThisGroup.getUniqueKey(), "users" + getQueryString(true));
} else {
if (ObjectUtils.isEmpty(getType())) {
throw new IllegalArgumentException(BaasioError.ERROR_MISSING_TYPE);
}
response = Baas.io().apiRequest(HttpMethod.GET, null, null,
getType() + getQueryString(true));
}
} else {
if (hasNextEntities()) {
response = Baas.io().apiRequest(HttpMethod.GET, null, null,
getRawString() + "&cursor=" + getNextCursor());
} else {
throw new BaasioException(BaasioError.ERROR_QUERY_NO_MORE_NEXT);
}
}
if (!ObjectUtils.isEmpty(response)) {
currentCursor = getNextCursor();
String nextCursor = response.getCursor();
if (!ObjectUtils.isEmpty(nextCursor)) {
cursorStack.push(nextCursor);
}
return response;
} else {
throw new BaasioException(BaasioError.ERROR_UNKNOWN_NO_RESPONSE_DATA);
}
}
/**
* Query next entities. Executes asynchronously in background and the
* callbacks are called in the UI thread.
*
* @param callback Result callback
*/
public void nextInBackground(final BaasioQueryCallback callback) {
(new BaasioQueryAsyncTask(this, callback) {
@Override
public BaasioResponse doTask() throws Exception {
return next();
}
}).execute();
}
}