/******************************************************************************
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
* the specific language governing rights and limitations under the License.
*
* The Original Code is: Jsoda
* The Initial Developer of the Original Code is: William Wong (williamw520@gmail.com)
* Portions created by William Wong are Copyright (C) 2012 William Wong, All Rights Reserved.
*
******************************************************************************/
package wwutil.jsoda;
import java.util.*;
import java.io.*;
import java.lang.reflect.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import wwutil.sys.FnUtil;
import wwutil.sys.FnUtil.*;
import wwutil.model.ValidationException;
import wwutil.model.annotation.CachePolicy;
import wwutil.model.annotation.CacheByField;
import wwutil.model.annotation.DefaultGUID;
import wwutil.model.annotation.DefaultComposite;
import wwutil.model.annotation.VersionLocking;
import wwutil.model.annotation.S3Field;
@SuppressWarnings("unchecked")
public class Dao<T>
{
private static Log log = LogFactory.getLog(Dao.class);
private Class<T> modelClass;
private String modelName;
private Jsoda jsoda;
public Dao(Class<T> modelClass, Jsoda jsoda) {
this.modelClass = modelClass;
this.modelName = jsoda.getModelName(modelClass);
this.jsoda = jsoda;
}
public void put(T dataObj)
throws JsodaException
{
try {
Field versionField = jsoda.getVersionField(modelName);
if (versionField == null) {
putIf(dataObj, null, null, false);
} else {
// Get old version as the expectedVersion before preStoreSteps() incrementing the version number.
Integer expectedVersion = (Integer)versionField.get(dataObj);
boolean expectedExists = (expectedVersion != null && expectedVersion.intValue() > 0);
expectedVersion = (expectedExists ? expectedVersion : new Integer(0));
putIf(dataObj, versionField.getName(), expectedVersion, expectedExists);
}
} catch(JsodaException je) {
throw je;
} catch(Exception e) {
throw new JsodaException("Failed to put object", e);
}
}
public void putIf(T dataObj, String expectedField, Object expectedValue, boolean expectedExists)
throws JsodaException
{
try {
jsoda.preStoreSteps(dataObj);
jsoda.getDb(modelName).putObj(modelClass, dataObj, expectedField, expectedValue, expectedExists);
jsoda.s3dao(modelClass).saveS3Fields(dataObj);
jsoda.getObjCacheMgr().cachePut(modelName, dataObj);
} catch(JsodaException je) {
throw je;
} catch(Exception e) {
throw new JsodaException("Failed to put object", e);
}
}
/** Support batch put on array of objects or varargs of objects */
public void batchPut(T... dataObjs)
throws JsodaException
{
batchPut(Arrays.asList(dataObjs));
}
public void batchPut(List<T> dataObjs)
throws JsodaException
{
if (dataObjs.size() == 0)
return;
try {
for (T dataObj : dataObjs) {
jsoda.preStoreSteps(dataObj);
}
jsoda.getDb(modelName).putObjs(modelClass, dataObjs);
for (T dataObj : dataObjs) {
jsoda.s3dao(modelClass).saveS3Fields(dataObj);
jsoda.getObjCacheMgr().cachePut(modelName, dataObj);
}
} catch(JsodaException je) {
throw je;
} catch(Exception e) {
throw new JsodaException("Failed to batch put objects", e);
}
}
public T get(Object id)
throws JsodaException
{
if (!(id instanceof Integer ||
id instanceof Long ||
id instanceof String))
throw new ValidationException("The Id can only be String, Integer, or Long.");
return getObj(id, null);
}
public T get(Object hashKey, Object rangeKey)
throws JsodaException
{
if (!(hashKey instanceof Integer ||
hashKey instanceof Long ||
hashKey instanceof String))
throw new ValidationException("The hashKey can only be String, Integer, or Long.");
if (!(rangeKey instanceof Integer ||
rangeKey instanceof Long ||
rangeKey instanceof String))
throw new ValidationException("The rangeKey can only be String, Integer, or Long.");
return getObj(hashKey, rangeKey);
}
private T getObj(Object id, Object rangeKey)
throws JsodaException
{
try {
T obj = (T)jsoda.getObjCacheMgr().cacheGet(modelName, id, rangeKey);
if (obj != null)
return obj;
if (rangeKey == null) {
if (jsoda.getRangeField(modelName) != null) {
throw new ValidationException("Model " + modelName + " requires rangeKey for get.");
}
obj = jsoda.getDb(modelName).getObj(modelClass, id, null);
} else {
obj = jsoda.getDb(modelName).getObj(modelClass, id, rangeKey);
}
if (obj != null) {
jsoda.s3dao(modelClass).loadS3Fields(obj);
jsoda.postLoadSteps(obj);
}
return obj;
} catch(JsodaException je) {
throw je;
} catch(Exception e) {
throw new JsodaException("Failed to get object", e);
}
}
public void delete(Object id)
throws JsodaException
{
if (!(id instanceof Integer ||
id instanceof Long ||
id instanceof String))
throw new ValidationException("The Id can only be String, Integer, or Long.");
if (jsoda.getRangeField(modelName) != null) {
throw new ValidationException("Model " + modelName + " requires rangeKey for delete.");
}
deleteObj(id, null);
}
public void delete(Object hashKey, Object rangeKey)
throws JsodaException
{
if (!(hashKey instanceof Integer ||
hashKey instanceof Long ||
hashKey instanceof String))
throw new ValidationException("The hashKey can only be String, Integer, or Long.");
if (!(rangeKey instanceof Integer ||
rangeKey instanceof Long ||
rangeKey instanceof String))
throw new ValidationException("The rangeKey can only be String, Integer, or Long.");
deleteObj(hashKey, rangeKey);
}
private void deleteObj(Object id, Object rangeKey)
throws JsodaException
{
try {
jsoda.getObjCacheMgr().cacheDelete(modelName, id, rangeKey);
if (rangeKey == null) {
jsoda.getDb(modelName).delete(modelName, id, null);
} else {
jsoda.getDb(modelName).delete(modelName, id, rangeKey);
}
jsoda.s3dao(modelClass).deleteS3Fields(id, rangeKey);
} catch(Exception e) {
throw new JsodaException("Failed to delete object " + id + "/" + rangeKey, e);
}
}
/** Support batch delete on array of objects or varargs of objects */
public void batchDelete(Object... idList)
throws JsodaException
{
batchDelete(Arrays.asList(idList));
}
public void batchDelete(List idList)
throws JsodaException
{
try {
for (Object id : idList) {
jsoda.getObjCacheMgr().cacheDelete(modelName, id, null);
}
jsoda.getDb(modelName).batchDelete(modelName, idList, null);
for (Object id : idList) {
jsoda.s3dao(modelClass).deleteS3Fields(id, null);
}
} catch(Exception e) {
throw new JsodaException("Failed to batch delete objects", e);
}
}
public void batchDelete(List idList, List rangeKeyList)
throws JsodaException
{
try {
for (int i = 0; i < idList.size(); i++) {
jsoda.getObjCacheMgr().cacheDelete(modelName, idList.get(i), rangeKeyList.get(i));
}
jsoda.getDb(modelName).batchDelete(modelName, idList, rangeKeyList);
for (int i = 0; i < idList.size(); i++) {
jsoda.s3dao(modelClass).deleteS3Fields(idList.get(i), rangeKeyList.get(i));
}
} catch(Exception e) {
throw new JsodaException("Failed to batch delete objects", e);
}
}
/** Get an object by one of its field, beside the Id field. */
public T findBy(String field, Object fieldValue)
throws JsodaException
{
T obj = (T)jsoda.getObjCacheMgr().cacheGetByField(modelName, field, fieldValue);
if (obj != null)
return obj;
List<T> items = jsoda.query(modelClass).eq(field, fieldValue).run();
// query.run() has already cached the object. No need to cache it here.
return items.size() == 0 ? null : items.get(0);
}
}