package com.fourspaces.featherdb.document;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.fourspaces.featherdb.backend.Backend;
import com.fourspaces.featherdb.utils.Logger;
abstract public class Document {
// build list of document content-type handling classes
private static Map<String,Class<? extends Document>> docTypes = new HashMap<String,Class<?extends Document>> ();
static {
loadType(JSONDocument.class);
loadType(BinaryDocument.class);
}
private static void loadType(Class<? extends Document> clazz) {
ContentTypes contentTypes = clazz.getAnnotation(ContentTypes.class);
for (String type:contentTypes.value()) {
// System.out.println(type+" => "+clazz);
docTypes.put(type.toLowerCase(), clazz);
}
}
// constants
public static final String CREATED_DATE = "_created_date";
public static final String REV_USER = "_rev_by";
public static final String REV_DATE = "_rev_date";
public static final String REV = "_rev";
public static final String DB = "_db";
public static final String ID = "_id";
public static final String CONTENT_TYPE = "_content_type";
public static final String DEFAULT_CONTENT_TYPE = "application/javascript";
// factory methods
public static Document newRevision(Backend backend, Document parent, String username) throws DocumentCreationException {
return newDocument(backend,parent.getDatabase(),parent.getId(),parent.getContentType(),parent.getClass(),parent.commonData, username);
}
public static Document newDocument(Backend backend,String database, String id, String contentType, String username) throws DocumentCreationException {
Class<? extends Document> clazz = docTypes.get(contentType.toLowerCase());
if (clazz==null) {
clazz = docTypes.get("*");
}
if (clazz==null) {
throw new DocumentCreationException("Could not find backing class for content type: "+contentType);
}
return newDocument(backend,database,id,contentType,clazz,username);
}
public static Document newDocument(Backend backend,String database, String id, String username) throws DocumentCreationException {
return newDocument(backend,database,id,DEFAULT_CONTENT_TYPE,username);
}
public static Document newDocument(Backend backend,String database, String id, String contentType, Class<? extends Document> clazz,String username) throws DocumentCreationException {
return newDocument(backend,database,id,contentType,clazz,null,username);
}
protected static Document newDocument(Backend backend,String database, String id, String contentType, Class<? extends Document> clazz, JSONObject commonData, String username) throws DocumentCreationException {
if (id==null) {
id=generateId(backend,database);
}
String revision=generateRevision(backend,database,id);
Document d;
try {
d = clazz.newInstance();
if (commonData==null) {
d.commonData = new JSONObject();
d.commonData.put(ID,id);
d.commonData.put(DB,database);
d.commonData.put(CREATED_DATE,new Date().getTime());
d.commonData.put(CONTENT_TYPE, contentType.toLowerCase());
d.commonDirty = true;
} else {
d.commonData= new JSONObject(commonData);
}
d.metaData = new JSONObject();
d.setRevision(revision);
d.setRevisionDate(new Date());
d.setRevisionUser(username);
d.dataDirty=true;
return d;
} catch (InstantiationException e) {
throw new DocumentCreationException(e);
} catch (IllegalAccessException e) {
throw new DocumentCreationException(e);
}
}
protected static final String generateId(Backend backend, String database) {
String id = null;
while (id==null || backend.doesDocumentExist(database, id)) {
UUID uuid = java.util.UUID.randomUUID();
id = Long.toHexString(uuid.getLeastSignificantBits())+ Long.toHexString(uuid.getMostSignificantBits());
}
return id;
}
protected static final String generateRevision(Backend backend,String database, String id) {
String rev = null;
while (rev==null || backend.doesDocumentRevisionExist(database, id,rev)) {
rev = Long.toHexString(new Random().nextLong());
}
//backend.touchRevision(database,id,revision);
return rev;
}
public static Document loadDocument(JSONObject commonJSON, JSONObject metaJSON) throws DocumentCreationException {
Class<? extends Document> clazz = docTypes.get(commonJSON.get(CONTENT_TYPE));
if (clazz==null) {
clazz = docTypes.get("*");
}
if (clazz==null) {
throw new DocumentCreationException("Could not find backing class for content type: "+commonJSON.get(CONTENT_TYPE));
}
try {
Document d = clazz.newInstance();
d.commonData=commonJSON;
d.metaData=metaJSON;
return d;
} catch (InstantiationException e) {
throw new DocumentCreationException(e);
} catch (IllegalAccessException e) {
throw new DocumentCreationException(e);
}
}
// end statics
abstract public void setRevisionData(InputStream dataInput) throws DocumentCreationException;
abstract public void sendDocument(OutputStream dataOutput, Map<String,String[]> params) throws IOException;
abstract public void writeRevisionData(OutputStream dataOutput) throws IOException;
abstract public boolean writesRevisionData();
protected JSONObject commonData=null;
protected JSONObject metaData=null;
protected boolean commonDirty = false;
protected boolean dataDirty = false;
protected Logger log = Logger.get(getClass());
protected void setRevision(String rev) {
metaData.put(REV,rev);
}
protected void setRevisionDate(Date date) {
metaData.put(REV_DATE,date.getTime());
}
protected void setRevisionDate(long timestamp) {
metaData.put(REV_DATE,timestamp);
}
protected void setRevisionUser(String revUser) {
metaData.put(REV_USER, revUser);
}
public Date getRevisionDate() {
return new Date(metaData.getLong(REV_DATE));
}
public String getRevision() {
return metaData.getString(REV);
}
public String getRevisionUser() {
return metaData.getString(REV_USER);
}
public Date getCreated() {
Long l = commonData.optLong(CREATED_DATE);
if (l!=null) {
return new Date(l);
}
return null;
}
public String getDatabase() {
try {
return commonData.getString(DB);
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
public String getId() {
try {
return commonData.getString(ID);
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
public String getContentType() {
try {
return commonData.getString(CONTENT_TYPE);
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
public JSONObject getCommonData() {
return commonData;
}
public JSONObject getMetaData() {
return metaData;
}
public void writeCommonData(Writer writer) throws IOException{
commonData.write(writer);
}
public void setRevisions(JSONArray revs) {
commonData.put("_revisions",revs);
}
public boolean isCommonDirty() {
return commonDirty;
}
public void setCommonDirty(boolean commonDirty) {
this.commonDirty = commonDirty;
}
public boolean isDataDirty() {
return dataDirty;
}
public void setDataDirty(boolean dataDirty) {
this.dataDirty = dataDirty;
}
public void writeMetaData(Writer writer, Map<String, String[]> params) throws IOException{
boolean pretty=false;
if (params.containsKey("pretty")) {
String[] values = params.get("pretty");
for (String value:values) {
if (value.equals("true")) {
pretty=true;
}
}
}
if (pretty) {
writer.write(toString(2));
} else {
writer.write(toString());
}
}
/**
* Non-indented version of toString which returns the common and meta JSON data
* @return
*/
public String toString() {
String jsonString = commonData.toString();
jsonString = jsonString.substring(0,jsonString.length()-1);
String revJSON = metaData.toString();
if (!"".equals(revJSON)) {
revJSON = revJSON.substring(1);
jsonString = jsonString + "," +revJSON;
} else {
jsonString+="}";
}
return jsonString;
}
/**
* Indented version of toString which returns the common and meta JSON data
* @param indent
* @return
*/
public String toString(int indent) {
String jsonString = commonData.toString(indent);
jsonString = jsonString.substring(0,jsonString.length()-2);
String revJSON = metaData.toString(indent);
if (!"".equals(revJSON)) {
revJSON = revJSON.substring(1);
jsonString = jsonString + "," +revJSON;
} else {
jsonString+="}";
}
return jsonString;
}
}