/***************************************************************************************** Infosistema - OpenBaas Copyright(C) 2002-2014 Infosistema, S.A. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. www.infosistema.com info@openbaas.com Av. José Gomes Ferreira, 11 3rd floor, s.34 Miraflores 1495-139 Algés Portugal ****************************************************************************************/ package infosistema.openbaas.dataaccess.models; import infosistema.openbaas.data.Metadata; import infosistema.openbaas.utils.Log; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; import com.mongodb.BasicDBObject; import com.mongodb.DBCollection; import com.mongodb.DBObject; public class DocumentModel extends ModelAbstract { // *** CONTRUCTORS *** // public DocumentModel() { super(); } // *** PRIVATE *** // private static BasicDBObject dataProjection = null; private static BasicDBObject dataProjectionMetadata = null; // *** PROTECTED *** // @Override protected DBCollection getCollection(String appId) { return super.getCollection(String.format(APP_DATA_COLL_FORMAT, appId)); } public String getDocumentPath(List<String> path) { if (path == null) return ""; StringBuilder sb = new StringBuilder(); for(int i = 0; i < path.size(); i++) if (!"".equals(path.get(i))) sb.append(path.get(i)).append("/"); if (sb.length() > 0) sb.deleteCharAt(sb.length()-1); return sb.toString(); } protected String getDocumentParentPath(List<String> path) { if (path == null) return ""; StringBuilder sb = new StringBuilder(); for(int i = 0; i < path.size() - 1; i++) if (!"".equals(path.get(i))) sb.append(path.get(i)).append("/"); if (sb.length() > 0) sb.deleteCharAt(sb.length()-1); return sb.toString(); } public String getDocumentId(String userId, List<String> path) { StringBuilder sb = new StringBuilder(); if (userId != null) sb.append(userId).append("/"); sb.append("data/"); if (path != null) { for(int i = 0; i < path.size(); i++) if (!"".equals(path.get(i))) sb.append(path.get(i)).append("/"); } sb.deleteCharAt(sb.length()-1); return sb.toString(); } protected String getDocumentKey(List<String> path) { String retObj = ""; if (path != null && path.size() > 0) { retObj = path.get(path.size() - 1); } return retObj; } @Override protected BasicDBObject getDataProjection(boolean getMetadata, List<String> toShow, List<String> toHide) { if (toShow != null && toShow.size()>0) { BasicDBObject projection = new BasicDBObject(); //Ciclo por string a 1 na projection Iterator<String> it = toShow.iterator(); while(it.hasNext()){ projection.append(it.next(), 1); } return projection; } else if (toHide != null && toHide.size()>0) { BasicDBObject projection = super.getDataProjection(new BasicDBObject(), getMetadata); projection.append(_KEY, 0); projection.append(_USER_ID, 0); projection.append(_PARENT_PATH, 0); //Ciclo por string a 0 na projection Iterator<String> it = toHide.iterator(); while(it.hasNext()){ projection.append(it.next(), 0); } return projection; } else if (getMetadata) { if (dataProjectionMetadata == null) { dataProjectionMetadata = super.getDataProjection(new BasicDBObject(), true); dataProjectionMetadata.append(_KEY, 0); dataProjectionMetadata.append(_USER_ID, 0); dataProjectionMetadata.append(_PARENT_PATH, 0); } return dataProjectionMetadata; } else { if (dataProjection == null) { dataProjection = super.getDataProjection(new BasicDBObject(), false); dataProjection.append(_KEY, 0); dataProjection.append(_USER_ID, 0); dataProjection.append(_PARENT_PATH, 0); } return dataProjection; } } // *** CONSTANTS *** // // *** KEYS *** // private static final String _PARENT_PATH = "_parentPath"; private static final String _KEY = "_key"; private static final String PARENT_PATH_QUERY_FORMAT = "{\"" + _PARENT_PATH + "\": \"%s\"}"; private static final String APP_DATA_COLL_FORMAT = "app%sdata"; // *** CREATE *** // public synchronized JSONObject insertDocumentInPath(String appId, String userId, List<String> path, JSONObject data, Map<String, String> extraMetadata) throws JSONException { deleteDocumentInPath(appId, userId, path); JSONObject newData = insertDocument(appId, userId, path, data, extraMetadata); if (updateAscendents(appId, userId, path, data, extraMetadata)) return newData; else return null; } private JSONObject insertDocument(String appId, String userId, List<String> path, JSONObject data, Map<String, String> metadata) throws JSONException{ if (!isMetadataCreate(metadata)) metadata = getMetadataCreate(userId, metadata); else metadata.remove(Metadata.LOCATION); try{ Iterator<?> it = data.keys(); while (it.hasNext()) { String key = (String) it.next(); Object value = data.get(key); if (value instanceof JSONObject) { List<String> newPath = addPath(path, key); insertDocument(appId, userId, newPath, (JSONObject)value, metadata); } } return insert(appId, userId, path, data, metadata); } catch (Exception e) { Log.error("", this, "insertDocumentInPath", "An error ocorred.", e); return null; } } private JSONObject insert(String appId, String userId, List<String> path, JSONObject value, Map<String, String> metadataFields) throws JSONException{ String id = getDocumentId(userId, path); JSONObject data = new JSONObject(value.toString()); data.put(_KEY, getDocumentKey(path)); data.put(_ID, id); if (userId != null && !"".equals(userId)) data.put(_USER_ID, userId); data.put(_PARENT_PATH, getDocumentParentPath(path)); if(!existsDocument(appId, userId, path) || !id.equals(userId)) { JSONObject metadata = getMetadaJSONObject(metadataFields); JSONObject geolocation = getGeolocation(metadata); super.insert(appId, data, metadata, geolocation); } return getDocument(appId, id, true, null, null);//<-- este true diz que é para devolver o metadata } // *** UPDATE *** // public synchronized JSONObject updateDocumentInPath(String appId, String userId, List<String> path, JSONObject data, Map<String, String> extraMetadata) throws JSONException{ JSONObject newData = null; try{ String id = getDocumentId(userId, path); Map<String, String> metadataUpdate = getMetadataUpdate(userId, extraMetadata); if (!existsNode(appId, id)) { newData = insertDocument(appId, userId, path, data, getMetadataCreate(userId, extraMetadata)); if (!updateAscendents(appId, userId, path, data, extraMetadata)) return null; } updateDocumentValues(appId, userId, path, data, true, metadataUpdate); newData = (JSONObject)getDocumentInPath(appId, userId, path, false, null, null); updateAscendents(appId, userId, path, newData, metadataUpdate); } catch (Exception e) { Log.error("", this, "updateDocumentInPath", "An error ocorred.", e); return null; } return newData; } private Boolean updateDocumentValues(String appId, String userId, List<String> path, JSONObject data, boolean updateChilds, Map<String, String> metadata) throws JSONException{ String id = getDocumentId(userId, path); Iterator<?> it = data.keys(); Map<String, String> metadataCreate = null; while (it.hasNext()) { String key = it.next().toString();; Object value = data.get(key); if (updateChilds && value instanceof JSONObject) { List<String> newPath = addPath(path, key); if (existsNode(appId, id + "/" + key)) { deleteDocument(appId, id+"/"+key); } if (metadataCreate == null) { metadataCreate = getMetadataCreate(userId, metadata); metadataCreate.remove(Metadata.LOCATION); } insertDocument(appId, userId, newPath, (JSONObject)value, metadataCreate); } updateDocumentValue(appId, id, key, value); } if (!isMetadataUpdate(metadata)) metadata = getMetadataUpdate(userId, metadata); updateMetadata(appId, id, metadata); return true; } private Boolean updateAscendents(String appId, String userId, List<String> path, JSONObject data, Map<String, String> metadata) throws JSONException{ if (!isMetadataUpdate(metadata)) metadata = getMetadataUpdate(userId, metadata); String key = getDocumentKey(path); path = removeLast(path); String id = getDocumentId(userId, path); if (!existsNode(appId, id)) insert(appId, userId, path, new JSONObject(), getMetadataCreate(userId, metadata)); if ("".equals(key)) { if (userId != null && !"".equals(userId)) updateDocumentValue(appId, userId, DATA, (JSONObject)getDocumentInPath(appId, userId, path, false, null, null)); return true; } else { updateDocumentValue(appId, id, key, data); return updateAscendents(appId, userId, path, (JSONObject)getDocumentInPath(appId, userId, path, false, null, null), metadata); } } // *** DELETE *** // public Boolean deleteDocumentInPath(String appId, String userId, List<String> path) throws JSONException{ String id = getDocumentId(userId, path); if (existsNode(appId, id)) { try{ List<DBObject> listToDel = getDocumentAndChilds(appId, id); Iterator<DBObject> itDoc = listToDel.iterator(); while(itDoc.hasNext()){ DBObject doc = itDoc.next(); deleteDocument(appId, doc.get(_ID).toString()); } removeKeyFromAscendents(appId, userId, "", path); } catch (Exception e) { Log.error("", this, "deleteDocumentInPath", "An error ocorred.", e); return false; } } else { try{ String key = getDocumentKey(path); path = removeLast(path); id = getDocumentId(userId, path); DBCollection coll = getCollection(appId); BasicDBObject ob = new BasicDBObject(); BasicDBObject dbRemove = new BasicDBObject(); ob.append(key, ""); dbRemove.append("$unset", ob); BasicDBObject dbQuery = new BasicDBObject(); dbQuery.append(_ID, id); coll.update(dbQuery, dbRemove); removeKeyFromAscendents(appId, userId, key, path); } catch (Exception e) { Log.error("", this, "deleteDocumentInPath", "An error ocorred.", e); return false; } } return true; } public Boolean removeKeyFromAscendents(String appId, String userId, String key, List<String> path) throws JSONException { if (path == null || path.size() <= 0) { if (userId != null && !"".equals(userId)) { super.removeKeyFromDocument(appId, userId, "data." + key); } return true; } String auxKey = getDocumentKey(path); key = auxKey + ((key == null || "".equals(key)) ? "" : "." + key); path = removeLast(path); String id = getDocumentId(userId, path); super.removeKeyFromDocument(appId, id, key); return removeKeyFromAscendents(appId, userId, key, path); } // *** GET LIST *** // @Override protected String getParentPathQueryString(String path) { return String.format(PARENT_PATH_QUERY_FORMAT, path); } // *** GET *** // public Object getDocumentInPath(String appId, String userId, List<String> path, boolean getMetadata, List<String> toShow, List<String> toHide) throws JSONException { String id = getDocumentId(userId, path); BasicDBObject searchQuery = new BasicDBObject(); searchQuery.append(_ID, id); if (existsNode(appId, id)) { return super.getDocument(appId, id, getMetadata, toShow, toHide); } else if (path != null && path.size() > 0) { String key = getDocumentKey(path); path = removeLast(path); id = getDocumentId(userId, path); if (existsNode(appId, id)) { List<String> lst = new ArrayList<String>(); lst.add(key); return super.getDocument(appId, id, false, lst, null).get(key); } } return null; } // *** EXISTS *** // public Boolean existsDocument(String appId, String userId, List<String> path) { return existsNode(appId, getDocumentId(userId, path)) || existsKey(appId, userId, path); } private Boolean existsKey(String appId, String userId, List<String> path) { if (path == null || path.size() <= 0) return false; String key = getDocumentKey(path); path = removeLast(path); String id = getDocumentId(userId, path); return super.existsKey(appId, id, key); } }