/*
* Copyright (c) www.bugull.com
*
* 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 net.tooan.ynpay.third.mongodb;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import net.tooan.ynpay.third.jfinal.log.Logger;
import net.tooan.ynpay.third.mongodb.annotations.Id;
import net.tooan.ynpay.third.mongodb.cache.FieldsCache;
import net.tooan.ynpay.third.mongodb.exception.DBQueryException;
import net.tooan.ynpay.third.mongodb.exception.FieldException;
import net.tooan.ynpay.third.mongodb.mapper.IdUtil;
import net.tooan.ynpay.third.mongodb.mapper.MapperUtil;
import net.tooan.ynpay.third.mongodb.mapper.Operator;
import net.tooan.ynpay.third.mongodb.mapper.ReferenceUtil;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
/**
* Convenient class for creating DBObject queries.
*
* @author Frank Wen(xbwen@hotmail.com)
*/
@SuppressWarnings("unchecked")
public class BuguQuery<T> {
private final static Logger logger = Logger.getLogger(BuguQuery.class);
private DBCollection coll;
private Class<T> clazz;
private DBObject keys;
private DBObject slices;
private DBObject fields;
private boolean fieldsSpecified = false;
private String orderBy;
private DBObject condition = new BasicDBObject();
private int pageNumber = 0; //default value is zero
private int pageSize = 0; //default value is zero
public BuguQuery(DBCollection coll, Class<T> clazz, DBObject keys) {
this.coll = coll;
this.clazz = clazz;
this.keys = keys;
}
private void appendEquals(String key, String op, Object value) {
if (key.indexOf(".") != -1) {
append(key, op, value);
} else {
Field f = null;
try {
f = FieldsCache.getInstance().getField(clazz, key);
} catch (FieldException ex) {
logger.error(ex.getMessage(), ex);
}
if (f.getAnnotation(Id.class) != null) {
Object dbId = IdUtil.toDbId(clazz, (String) value);
append(Operator.ID, op, dbId);
} else if (value instanceof BuguEntity) {
BuguEntity ent = (BuguEntity) value;
Object refObj = ReferenceUtil.toDbReference(clazz, key, ent.getClass(), ent.getId());
append(key, op, refObj);
} else {
append(key, op, value);
}
}
}
private List<Object> toIds(Object... values) {
List<Object> idList = new ArrayList<Object>();
int len = values.length;
for (int i = 0; i < len; i++) {
if (values[i] != null) {
Object dbId = IdUtil.toDbId(clazz, (String) values[i]);
idList.add(dbId);
}
}
return idList;
}
private List<Object> toReferenceList(String key, Object... values) {
List<Object> refList = new ArrayList<Object>();
int len = values.length;
for (int i = 0; i < len; i++) {
if (values[i] != null) {
BuguEntity ent = (BuguEntity) values[i];
Object refObj = ReferenceUtil.toDbReference(clazz, key, ent.getClass(), ent.getId());
refList.add(refObj);
}
}
return refList;
}
private void appendIn(String key, String op, Object... values) {
if (key.equals(Operator.ID)) {
append(key, op, toIds(values));
} else if (key.indexOf(".") != -1) {
append(key, op, values);
} else {
Field f = null;
try {
f = FieldsCache.getInstance().getField(clazz, key);
} catch (FieldException ex) {
logger.error(ex.getMessage(), ex);
}
if (f.getAnnotation(Id.class) != null) {
append(Operator.ID, op, toIds(values));
} else if (values[0] instanceof BuguEntity) {
append(key, op, toReferenceList(key, values));
} else {
append(key, op, values);
}
}
}
private void append(String key, String op, Object value) {
if (op == null) {
condition.put(key, value);
return;
}
Object obj = condition.get(key);
DBObject dbo = null;
if (!(obj instanceof DBObject)) {
dbo = new BasicDBObject(op, value);
condition.put(key, dbo);
} else {
dbo = (DBObject) condition.get(key);
dbo.put(op, value);
}
}
public BuguQuery<T> is(String key, Object value) {
appendEquals(key, null, value);
return this;
}
public BuguQuery<T> notEquals(String key, Object value) {
appendEquals(key, Operator.NE, value);
return this;
}
public BuguQuery<T> or(BuguQuery... querys) {
List list = (List) condition.get(Operator.OR);
if (list == null) {
list = new ArrayList();
condition.put(Operator.OR, list);
}
for (BuguQuery q : querys) {
list.add(q.getCondition());
}
return this;
}
public BuguQuery<T> and(BuguQuery... querys) {
List list = (List) condition.get(Operator.AND);
if (list == null) {
list = new ArrayList();
condition.put(Operator.AND, list);
}
for (BuguQuery q : querys) {
list.add(q.getCondition());
}
return this;
}
public BuguQuery<T> greaterThan(String key, Object value) {
append(key, Operator.GT, value);
return this;
}
public BuguQuery<T> greaterThanEquals(String key, Object value) {
append(key, Operator.GTE, value);
return this;
}
public BuguQuery<T> lessThan(String key, Object value) {
append(key, Operator.LT, value);
return this;
}
public BuguQuery<T> lessThanEquals(String key, Object value) {
append(key, Operator.LTE, value);
return this;
}
public BuguQuery<T> in(String key, List list) {
if (list == null || list.isEmpty()) {
return this;
}
return in(key, list.toArray());
}
public BuguQuery<T> in(String key, Object... values) {
appendIn(key, Operator.IN, values);
return this;
}
public BuguQuery<T> notIn(String key, List list) {
if (list == null || list.isEmpty()) {
return this;
}
return notIn(key, list.toArray());
}
public BuguQuery<T> notIn(String key, Object... values) {
appendIn(key, Operator.NIN, values);
return this;
}
public BuguQuery<T> all(String key, List list) {
if (list == null || list.isEmpty()) {
return this;
}
return all(key, list.toArray());
}
public BuguQuery<T> all(String key, Object... values) {
append(key, Operator.ALL, values);
return this;
}
/**
* Note: the regex string must in Java style, not JavaScript style.
*
* @param key
* @param regex
* @return
*/
public BuguQuery<T> regex(String key, String regex) {
append(key, null, Pattern.compile(regex));
return this;
}
public BuguQuery<T> size(String key, int value) {
append(key, Operator.SIZE, value);
return this;
}
public BuguQuery<T> mod(String key, int divisor, int remainder) {
append(key, Operator.MOD, new int[]{divisor, remainder});
return this;
}
public BuguQuery<T> existsField(String key) {
append(key, Operator.EXISTS, Boolean.TRUE);
return this;
}
public BuguQuery<T> notExistsField(String key) {
append(key, Operator.EXISTS, Boolean.FALSE);
return this;
}
public BuguQuery<T> where(String whereStr) {
append(Operator.WHERE, null, whereStr);
return this;
}
public BuguQuery<T> withinCenter(String key, double x, double y, double radius) {
DBObject dbo = new BasicDBObject(Operator.CENTER, new Object[]{new Double[]{x, y}, radius});
append(key, Operator.WITHIN, dbo);
return this;
}
public BuguQuery<T> withinBox(String key, double x1, double y1, double x2, double y2) {
DBObject dbo = new BasicDBObject(Operator.BOX, new Object[]{new Double[]{x1, y1}, new Double[]{x2, y2}});
append(key, Operator.WITHIN, dbo);
return this;
}
public BuguQuery<T> near(String key, double x, double y) {
append(key, Operator.NEAR, new Double[]{x, y});
return this;
}
public BuguQuery<T> near(String key, double x, double y, double maxDistance) {
append(key, Operator.NEAR, new Double[]{x, y, maxDistance});
return this;
}
public BuguQuery<T> slice(String key, long num) {
DBObject dbo = new BasicDBObject(Operator.SLICE, num);
return addSlice(key, dbo);
}
public BuguQuery<T> slice(String key, long begin, long length) {
DBObject dbo = new BasicDBObject(Operator.SLICE, new Long[]{begin, length});
return addSlice(key, dbo);
}
private BuguQuery<T> addSlice(String key, DBObject dbo) {
if (slices == null) {
slices = new BasicDBObject();
}
slices.put(key, dbo);
keys.put(key, dbo);
if (fields == null) {
fields = new BasicDBObject();
}
fields.put(key, dbo);
return this;
}
public BuguQuery<T> returnFields(String... fieldNames) {
return specifyFields(1, fieldNames);
}
public BuguQuery<T> notReturnFields(String... fieldNames) {
return specifyFields(0, fieldNames);
}
private BuguQuery<T> specifyFields(int value, String... fieldNames) {
if (fields == null) {
fields = new BasicDBObject();
}
for (String field : fieldNames) {
//do not replace the $slice, if has set
if (fields.get(field) == null) {
fields.put(field, value);
}
}
fieldsSpecified = true;
return this;
}
public BuguQuery<T> sort(String orderBy) {
this.orderBy = orderBy;
return this;
}
public BuguQuery<T> pageNumber(int pageNumber) {
this.pageNumber = pageNumber;
return this;
}
public BuguQuery<T> pageSize(int pageSize) {
this.pageSize = pageSize;
return this;
}
private void checkSingle() throws DBQueryException {
if (orderBy != null || pageNumber != 0 || pageSize != 0) {
throw new DBQueryException("You should use results() to get a list, when you use sorting or pagination");
}
}
public T result() {
try {
checkSingle();
} catch (DBQueryException ex) {
logger.error(ex.getMessage(), ex);
}
DBObject dbo = null;
if (fieldsSpecified) {
dbo = coll.findOne(condition, fields);
} else if (slices != null) {
dbo = coll.findOne(condition, slices);
} else {
dbo = coll.findOne(condition);
}
return MapperUtil.fromDBObject(clazz, dbo);
}
public List<T> results() {
DBCursor cursor = null;
if (fieldsSpecified) {
cursor = coll.find(condition, fields);
} else {
cursor = coll.find(condition, keys);
}
if (orderBy != null) {
cursor.sort(MapperUtil.getSort(orderBy));
}
if (pageNumber > 0 && pageSize > 0) {
cursor.skip((pageNumber - 1) * pageSize).limit(pageSize);
}
return MapperUtil.toList(clazz, cursor);
}
public long count() {
return coll.count(condition);
}
public boolean exists() {
DBObject dbo = coll.findOne(condition);
return dbo != null;
}
public List distinct(String key) {
return coll.distinct(key, condition);
}
public DBObject getCondition() {
return condition;
}
}