Java tutorial
/* * Copyright (C) 2014. BaasBox * * 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 com.baasbox.android; import android.content.ContentValues; import android.os.Parcel; import android.os.Parcelable; import com.baasbox.android.impl.Logger; import com.baasbox.android.impl.Util; import com.baasbox.android.json.JsonArray; import com.baasbox.android.json.JsonObject; import com.baasbox.android.json.JsonStructure; import com.baasbox.android.net.HttpRequest; import org.apache.http.HttpResponse; import java.util.*; /** * Represents a BaasBox document. * <p> * A document is a schema less JSON like entity that belongs to a given collection on the server. * </p> * <p> * Documents can be created, stored and retrieved from the server, either synchronously or asynchronously, * through the provided methods. * </p> * <p> * Unlike a raw JSON document, some key names at the top level are reserved: you cannot assign or create properties whose names begin with * an underscore or an at sign, the <em>id</em> field is also reserved. * </p> * <p> * When a document is bound to an entity on the server it's 'id' can be retrieved through {@link #getId()}. * Documents are versioned by BaasBox and updates with incompatible versions will fail, uless you ignore the * versioning explicitly through {@link com.baasbox.android.SaveMode#IGNORE_VERSION}. * </p> * * @author Andrea Tortorella * @since 0.7.3 */ public final class BaasDocument extends BaasObject implements Iterable<Map.Entry<String, Object>>, Parcelable { // ------------------------------ FIELDS ------------------------------ public static Creator<BaasDocument> CREATOR = new Creator<BaasDocument>() { @Override public BaasDocument createFromParcel(Parcel source) { return new BaasDocument(source); } @Override public BaasDocument[] newArray(int size) { return new BaasDocument[size]; } }; private final JsonWrapper data; private final String collection; private String id; private String author; private String creation_date; private String rid; private long version; // --------------------------- CONSTRUCTORS --------------------------- /** * Returns a new BaasDocument from it's raw json representation * @param data * @return a new BaasDocument */ public static BaasDocument from(JsonObject data) { return new BaasDocument(data); } BaasDocument(JsonObject o) { super(); JsonWrapper data = new JsonWrapper(o); this.collection = data.getString("@class"); data.remove("@class"); this.id = data.getString("id"); data.remove("id"); this.author = data.getString("_author"); data.remove("_author"); this.creation_date = data.getString("_creation_date"); data.remove("_creation_date"); this.version = data.getLong("@version"); data.remove("@version"); this.rid = data.getString("@rid"); data.remove("@rid"); this.data = data; } @Override public final boolean isDocument() { return true; } @Override public final boolean isFile() { return false; } public JsonObject toJson() { JsonObject json = data.copy(); json.put("@class", collection); json.put("id", id); json.put("_author", author); json.put("_creation_date", creation_date); json.put("@version", version); json.put("@rid", rid); return json; } /** * Creates a new unbound empty document belonging to the given <code>collection</code> * * @param collection a non empty collection name. * @throws java.lang.IllegalArgumentException if collection name is empty */ public BaasDocument(String collection) { this(collection, (JsonObject) null); } BaasDocument(Parcel source) { this.collection = source.readString(); this.id = Util.readOptString(source); this.version = source.readLong(); this.author = Util.readOptString(source); this.creation_date = Util.readOptString(source); this.rid = Util.readOptString(source); this.data = source.readParcelable(JsonWrapper.class.getClassLoader()); } /** * Creates a new unbound document that belongs to the given <code>collection</code> * and with fields initialized from the <code>data</code> {@link com.baasbox.android.json.JsonObject}. * <p/> * Data cannot contain reserved property names at the top level. * Note that the JSON data is copied in the document, so modifications to the original instance will * not be reflected by the document. * * @param collection a non empty collection name. * @param data a possibly null {@link com.baasbox.android.json.JsonObject} * @throws java.lang.IllegalArgumentException if collection name is empty or data contains reserved fields */ public BaasDocument(String collection, JsonObject data) { super(); if (collection == null || collection.length() == 0) throw new IllegalArgumentException("collection name cannot be null"); this.collection = collection; data = checkObject(data); //fixme we copy the data to avoid insertion of forbidden fields, but this is a costly operation this.data = new JsonWrapper(data); this.data.setDirty(true); } private static JsonWrapper checkObject(JsonObject data) { if (data == null) return null; if (data.contains("id")) throw new IllegalArgumentException("key 'id' is reserved"); for (String k : data.fields()) { char f = k.charAt(0); switch (f) { case '@': case '_': throw new IllegalArgumentException("key names starting with '_' or '@' are reserved"); } } return new JsonWrapper(data); } /** * Creates a new unbound document that belongs to the given <code>collection</code> * and with fields initialized from the <code>values</code> {@link android.content.ContentValues} * values are converted to a {@link com.baasbox.android.json.JsonObject} * using {@link com.baasbox.android.json.JsonObject#from(android.content.ContentValues)} * * @param collection a non empty collection name. * @param values a possibly null {@link android.content.ContentValues} * @throws java.lang.IllegalArgumentException if collection name is empty or values contains reserved fields names */ public BaasDocument(String collection, ContentValues values) { super(); if (collection == null || collection.length() == 0) throw new IllegalArgumentException("collection name cannot be null"); this.collection = collection; this.data = values == null ? new JsonWrapper() : checkObject(JsonObject.from(values)); } // -------------------------- STATIC METHODS -------------------------- private static JsonObject cleanObject(JsonObject data) { if (data == null) return new JsonObject(); data.remove("id"); for (String k : data.fields()) { char f = k.charAt(0); switch (f) { case '@': case '_': data.remove(k); break; } } return data; } ///--------------------- REQUESTS ------------------------------ /** * Asynchronously retrieves the list of documents readable to the user in <code>collection</code>. * * @param collection the collection to retrieve not <code>null</code> * @param handler a callback to be invoked with the result of the request * @return a {@link com.baasbox.android.RequestToken} to handle the asynchronous request */ public static RequestToken fetchAll(String collection, BaasHandler<List<BaasDocument>> handler) { return fetchAll(collection, null, RequestOptions.DEFAULT, handler); } /** * Asynchronously retrieves the list of documents readable to the user that match <code>filter</code> * in <code>collection</code> * * @param collection the collection to retrieve not <code>null</code> * @param filter a filter to apply to the request * @param handler a callback to be invoked with the result of the request * @return a {@link com.baasbox.android.RequestToken} to handle the asynchronous request */ public static RequestToken fetchAll(String collection, BaasQuery.Criteria filter, BaasHandler<List<BaasDocument>> handler) { return fetchAll(collection, filter, RequestOptions.DEFAULT, handler); } /** * Asynchronously retrieves the list of documents readable to the user * in <code>collection</code> * * @param collection the collection to retrieve not <code>null</code> * @param flags {@link RequestOptions} * @param handler a callback to be invoked with the result of the request * @return a {@link com.baasbox.android.RequestToken} to handle the asynchronous request */ public static RequestToken fetchAll(String collection, BaasQuery.Criteria filter, int flags, BaasHandler<List<BaasDocument>> handler) { BaasBox box = BaasBox.getDefaultChecked(); if (collection == null) throw new IllegalArgumentException("collection cannot be null"); Fetch f = new Fetch(box, collection, filter, flags, handler); return box.submitAsync(f); } public static BaasResult<List<BaasDocument>> fetchAllSync(String collection) { return fetchAllSync(collection, null); } /** * Synchronously retrieves the list of documents readable to the user * in <code>collection</code> * * @param collection the collection to retrieve not <code>null</code> * @return the result of the request */ public static BaasResult<List<BaasDocument>> fetchAllSync(String collection, BaasQuery.Criteria filter) { BaasBox box = BaasBox.getDefaultChecked(); if (collection == null) throw new IllegalArgumentException("collection cannot be null"); Fetch f = new Fetch(box, collection, filter, RequestOptions.DEFAULT, null); return box.submitSync(f); } /** * Asynchronously retrieves the number of documents readable to the user in <code>collection</code>. * * @param collection the collection to count not <code>null</code> * @param handler a callback to be invoked with the result of the request * @return a {@link com.baasbox.android.RequestToken} to handle the asynchronous request */ public static RequestToken count(String collection, BaasHandler<Long> handler) { return count(collection, null, RequestOptions.DEFAULT, handler); } /** * Asynchronously retrieves the number of documents readable to the user that match the <code>filter</code> * in <code>collection</code>. * * @param collection the collection to count not <code>null</code> * @param filter a {@link BaasQuery.Criteria} to apply to the request. May be <code>null</code> * @param handler a callback to be invoked with the result of the request * @return a {@link com.baasbox.android.RequestToken} to handle the asynchronous request */ public static RequestToken count(String collection, BaasQuery.Criteria filter, BaasHandler<Long> handler) { return count(collection, filter, RequestOptions.DEFAULT, handler); } /** * Asynchronously retrieves the number of documents readable to the user in <code>collection</code> * * @param collection the collection to count not <code>null</code> * @param flags {@link RequestOptions} * @param handler a callback to be invoked with the result of the request * @return a {@link com.baasbox.android.RequestToken} to handle the asynchronous request */ public static RequestToken count(String collection, int flags, BaasHandler<Long> handler) { return count(collection, null, flags, handler); } /** * Asynchronously retrieves the number of documents readable to the user that match the <code>filter</code> * in <code>collection</code> * * @param collection the collection to count not <code>null</code> * @param filter a {@link BaasQuery.Criteria} to apply to the request. May be <code>null</code> * @param handler a callback to be invoked with the result of the request * @return a {@link com.baasbox.android.RequestToken} to handle the asynchronous request */ private static RequestToken count(String collection, BaasQuery.Criteria filter, int flags, BaasHandler<Long> handler) { BaasBox box = BaasBox.getDefaultChecked(); if (collection == null) throw new IllegalArgumentException("collection cannot be null"); Count count = new Count(box, collection, filter, flags, handler); return box.submitAsync(count); } /** * Synchronously retrieves the number of document readable to the user in <code>collection</code> * * @param collection the collection to count not <code>null</code> * @return the result of the request */ public static BaasResult<Long> countSync(String collection) { return countSync(collection, null); } /** * Synchronously retrieves the number of document readable to the user that match <code>filter</code> * in <code>collection</code> * * @param collection the collection to count not <code>null</code> * @param filter a filter to apply to the request * @return the result of the request */ public static BaasResult<Long> countSync(String collection, BaasQuery.Criteria filter) { BaasBox box = BaasBox.getDefaultChecked(); if (collection == null) throw new IllegalArgumentException("collection cannot be null"); Count request = new Count(box, collection, filter, RequestOptions.DEFAULT, null); return box.submitSync(request); } /** * Asynchronously fetches the document identified by <code>id</code> in <code>collection</code> * * @param collection the collection to retrieve the document from. Not <code>null</code> * @param id the id of the document to retrieve. Not <code>null</code> * @param handler a callback to be invoked with the result of the request * @return a {@link com.baasbox.android.RequestToken} to handle the asynchronous request */ public static RequestToken fetch(String collection, String id, BaasHandler<BaasDocument> handler) { return fetch(collection, id, RequestOptions.DEFAULT, handler); } private static RequestToken fetch(String collection, String id, int flags, BaasHandler<BaasDocument> handler) { if (collection == null) throw new IllegalArgumentException("collection cannot be null"); if (id == null) throw new IllegalStateException("this document is not bound to any remote entity"); BaasDocument doc = new BaasDocument(collection); doc.id = id; return doc.refresh(flags, handler); } /** * Asynchronously refresh the content of this document. * * @param handler a callback to be invoked with the result of the request * @return a {@link com.baasbox.android.RequestToken} to handle the asynchronous request * @throws java.lang.IllegalStateException if this document has no id */ private RequestToken refresh(int flags, BaasHandler<BaasDocument> handler) { BaasBox box = BaasBox.getDefaultChecked(); if (handler == null) throw new IllegalArgumentException("handler cannot be null"); if (id == null) throw new IllegalStateException("this document is not bound to any remote entity"); Refresh refresh = new Refresh(box, this, flags, handler); return box.submitAsync(refresh); } /** * Synchronously fetches a document from the server * * @param collection the collection to retrieve the document from. Not <code>null</code> * @param id the id of the document to retrieve. Not <code>null</code> * @return the result of the request */ public static BaasResult<BaasDocument> fetchSync(String collection, String id) { if (collection == null) throw new IllegalArgumentException("collection cannot be null"); if (id == null) throw new IllegalArgumentException("id cannot be null"); BaasDocument doc = new BaasDocument(collection); doc.id = id; return doc.refreshSync(); } /** * Synchronously refresh the content of this document * * @return the result of the request * @throws java.lang.IllegalStateException if this document has no id */ public BaasResult<BaasDocument> refreshSync() { BaasBox box = BaasBox.getDefaultChecked(); if (id == null) throw new IllegalStateException("this document is not bound to any remote entity"); Refresh refresh = new Refresh(box, this, RequestOptions.DEFAULT, null); return box.submitSync(refresh); } public static RequestToken delete(String collection, String id, BaasHandler<Void> handler) { return delete(collection, id, RequestOptions.DEFAULT, handler); } public static RequestToken delete(String collection, String id, int flags, BaasHandler<Void> handler) { BaasBox box = BaasBox.getDefaultChecked(); if (collection == null) throw new IllegalArgumentException("collection cannot be null"); if (id == null) throw new IllegalArgumentException("id cannot be null"); Delete delete = new Delete(box, collection, id, flags, handler); return box.submitAsync(delete); } public static BaasResult<Void> deleteSync(String collection, String id) { if (collection == null) throw new IllegalArgumentException("collection cannot be null"); if (id == null) throw new IllegalArgumentException("id cannot be null"); BaasBox box = BaasBox.getDefaultChecked(); Delete delete = new Delete(box, collection, id, RequestOptions.DEFAULT, null); return box.submitSync(delete); } public RequestToken save(SaveMode mode, int flags, BaasHandler<BaasDocument> handler) { BaasBox box = BaasBox.getDefaultChecked(); if (mode == null) throw new IllegalArgumentException("mode cannot be null"); Save save = new Save(box, mode, this, flags, handler); return box.submitAsync(save); } public BaasResult<BaasDocument> saveSync(SaveMode mode) { BaasBox box = BaasBox.getDefaultChecked(); if (mode == null) throw new IllegalArgumentException("mode cannot be null"); Save save = new Save(box, mode, this, RequestOptions.DEFAULT, null); return box.submitSync(save); } // --------------------- GETTER / SETTER METHODS --------------------- @Override public final String getAuthor() { return author; } /** * Returns the collection to which this document belongs. * * @return the name of the collection */ public final String getCollection() { return collection; } @Override public final String getId() { return id; } @Override public final long getVersion() { return version; } // ------------------------ INTERFACE METHODS ------------------------ // --------------------- Interface Iterable --------------------- /** * Returns an {@link java.util.Iterator} over the mappings of this document * * @return an iterator over the mappings of this document */ @Override public Iterator<Map.Entry<String, Object>> iterator() { return data.iterator(); } // --------------------- Interface Parcelable --------------------- @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(collection); Util.writeOptString(dest, id); dest.writeLong(version); Util.writeOptString(dest, author); Util.writeOptString(dest, creation_date); Util.writeOptString(dest, rid); dest.writeParcelable(data, 0); } // -------------------------- OTHER METHODS -------------------------- /** * Removes all the mappings from this document * * @return this document with no mappings */ public BaasDocument clear() { data.clear(); return this; } /** * Checks if this document contains a mapping with <code>name</code> key * * @param name a non <code>null</code> key * @return <code>true</code> if the document contains the mapping <code>false</code> otherwise */ public boolean contains(String name) { return data.contains(name); } public RequestToken delete(BaasHandler<Void> handler) { return delete(RequestOptions.DEFAULT, handler); } public RequestToken delete(int flags, BaasHandler<Void> handler) { BaasBox box = BaasBox.getDefaultChecked(); if (id == null) throw new IllegalStateException("this document is not bound to any remote entity"); Delete delete = new Delete(box, this, flags, handler); return box.submitAsync(delete); } public BaasResult<Void> deleteSync() { if (id == null) throw new IllegalStateException("this document is not bound to any remote entity"); BaasBox box = BaasBox.getDefaultChecked(); Delete delete = new Delete(box, this, RequestOptions.DEFAULT, null); return box.submitSync(delete); } /** * Returns the value mapped to <code>name</code> as a {@link com.baasbox.android.json.JsonArray} * or <code>null</code> if the mapping is absent. * * @param name a non <code>null</code> key * @return the value mapped to <code>name</code> or <code>null</code> */ public JsonArray getArray(String name) { return data.getArray(name); } /** * Returns the value mapped to <code>name</code> as a {@link com.baasbox.android.json.JsonArray} * or <code>otherwise</code> if the mapping is absent. * * @param name a non <code>null</code> key * @param otherwise a default value * @return the value mapped to <code>name</code> or <code>otherwise</code> */ public JsonArray getArray(String name, JsonArray otherwise) { return data.getArray(name, otherwise); } /** * Returns the value mapped to <code>name</code> as a <code>byte[]</code> array * or <code>null</code> if the mapping is absent. * * @param name a non <code>null</code> key * @return the value mapped to <code>name</code> or <code>null</code> */ public byte[] getBinary(String name) { return data.getBinary(name); } /** * Returns the value mapped to <code>name</code> as a <code>byte[]</code> array * or <code>otherwise</code> if the mapping is absent. * * @param name a non <code>null</code> key * @param otherwise a default value * @return the value mapped to <code>name</code> or <code>otherwise</code> */ public byte[] getBinary(String name, byte[] otherwise) { return data.getBinary(name, otherwise); } /** * Returns the value mapped to <code>name</code> as a {@link java.lang.Boolean} * or <code>null</code> if the mapping is absent. * * @param name a non <code>null</code> key * @return the value mapped to <code>name</code> or <code>null</code> */ public Boolean getBoolean(String name) { return data.getBoolean(name); } /** * Returns the value mapped to <code>name</code> as a <code>boolean</code> * or <code>otherwise</code> if the mapping is absent. * * @param otherwise a <code>boolean</code> default * @param name a non <code>null</code> key * @return the value mapped to <code>name</code> or <code>otherwise</code> */ public boolean getBoolean(String name, boolean otherwise) { return data.getBoolean(name, otherwise); } @Override public final String getCreationDate() { return creation_date; } /** * Returns the value mapped to <code>name</code> as a {@link java.lang.Double} * or <code>null</code> if the mapping is absent. * * @param name a non <code>null</code> key * @return the value mapped to <code>name</code> or <code>null</code> */ public Double getDouble(String name) { return data.getDouble(name); } /** * Returns the value mapped to <code>name</code> as a <code>double</code> * or <code>otherwise</code> if the mapping is absent. * * @param otherwise a <code>double</code> default * @param name a non <code>null</code> key * @return the value mapped to <code>name</code> or <code>otherwise</code> */ public double getDouble(String name, double otherwise) { return data.getDouble(name, otherwise); } /** * Returns a {@link java.util.Set<java.lang.String>} of all the keys contained in this document * * @return a set of the keys contained in this document */ public Set<String> fields() { return data.fields(); } /** * Returns the value mapped to <code>name</code> as a {@link java.lang.Float} * or <code>null</code> if the mapping is absent. * * @param name a non <code>null</code> key * @return the value mapped to <code>name</code> or <code>null</code> */ public Float getFloat(String name) { return data.getFloat(name); } /** * Returns the value mapped to <code>name</code> as a <code>float</code> * or <code>otherwise</code> if the mapping is absent. * * @param otherwise a <code>float</code> default * @param name a non <code>null</code> key * @return the value mapped to <code>name</code> or <code>otherwise</code> */ public float getFloat(String name, float otherwise) { return data.getFloat(name, otherwise); } /** * Returns the value mapped to <code>name</code> as a {@link java.lang.Integer} * or <code>null</code> if the mapping is absent. * * @param name a non <code>null</code> key * @return the value mapped to <code>name</code> or <code>null</code> */ public Integer getInt(String name) { return data.getInt(name); } /** * Returns the value mapped to <code>name</code> as a <code>int</code> * or <code>otherwise</code> if the mapping is absent. * * @param otherwise a <code>int</code> default * @param name a non <code>null</code> key * @return the value mapped to <code>name</code> or <code>otherwise</code> */ public int getInt(String name, int otherwise) { return data.getInt(name, otherwise); } /** * Returns the value mapped to <code>name</code> as a {@link java.lang.Long} * or <code>null</code> if the mapping is absent. * * @param name a non <code>null</code> key * @return the value mapped to <code>name</code> or <code>null</code> */ public Long getLong(String name) { return data.getLong(name); } /** * Returns the value mapped to <code>name</code> as a <code>long</code> * or <code>otherwise</code> if the mapping is absent. * * @param otherwise a <code>long</code> default * @param name a non <code>null</code> key * @return the value mapped to <code>name</code> or <code>otherwise</code> */ public long getLong(String name, long otherwise) { return data.getLong(name, otherwise); } /** * Returns the value mapped to <code>name</code> as a {@link com.baasbox.android.json.JsonObject} * or <code>null</code> if the mapping is absent. * * @param name a non <code>null</code> key * @return the value mapped to <code>name</code> or <code>null</code> */ public JsonObject getObject(String name) { return data.getObject(name); } /** * Returns the value mapped to <code>name</code> as a {@link com.baasbox.android.json.JsonObject} * or <code>otherwise</code> if the mapping is absent. * * @param name a non <code>null</code> key * @param otherwise a default value * @return the value mapped to <code>name</code> or <code>otherwise</code> */ public JsonObject getObject(String name, JsonObject otherwise) { return data.getObject(name, otherwise); } /** * Returns the value mapped to <code>name</code> as a {@link java.lang.String} * or <code>null</code> if the mapping is absent. * * @param name a non <code>null</code> key * @return the value mapped to <code>name</code> or <code>null</code> */ public String getString(String name) { return data.getString(name); } /** * Returns the value mapped to <code>name</code> as a {@link java.lang.String} * or <code>otherwise</code> if the mapping is absent. * * @param name a non <code>null</code> key * @param otherwise a default value * @return the value mapped to <code>name</code> or <code>otherwise</code> */ public String getString(String name, String otherwise) { return data.getString(name, otherwise); } /** * Returns the value mapped to <code>name</code> as a {@link com.baasbox.android.json.JsonStructure} * or <code>null</code> if the mapping is absent. * * @param name a non <code>null</code> key * @return the value mapped to <code>name</code> or <code>null</code> */ public JsonStructure getStructure(String name) { return data.getStructure(name); } /** * Returns the value mapped to <code>name</code> as a {@link com.baasbox.android.json.JsonStructure} * or <code>otherwise</code> if the mapping is absent. * * @param name a non <code>null</code> key * @param otherwise a default value * @return the value mapped to <code>name</code> or <code>otherwise</code> */ public JsonStructure getStructure(String name, JsonStructure otherwise) { return data.getStructure(name, otherwise); } @Override public RequestToken grant(Grant grant, String username, int flags, BaasHandler<Void> handler) { BaasBox box = BaasBox.getDefaultChecked(); Access access = new Access(box, true, false, collection, id, username, grant, flags, handler); return box.submitAsync(access); } @Override public RequestToken grantAll(Grant grant, String role, int flags, BaasHandler<Void> handler) { BaasBox box = BaasBox.getDefaultChecked(); Access access = new Access(box, true, true, collection, id, role, grant, flags, handler); return box.submitAsync(access); } @Override public BaasResult<Void> grantAllSync(Grant grant, String role) { BaasBox box = BaasBox.getDefaultChecked(); Access access = new Access(box, true, true, collection, id, role, grant, RequestOptions.DEFAULT, null); return box.submitSync(access); } @Override public BaasResult<Void> grantSync(Grant grant, String username) { BaasBox box = BaasBox.getDefaultChecked(); Access access = new Access(box, true, false, collection, id, username, grant, RequestOptions.DEFAULT, null); return box.submitSync(access); } /** * Checks if <code>name</code> maps explicitly to <code>null</code> * * @param name a non <code>null</code> key * @return <code>true</code> if the document contains a mapping from <code>name</code> to <code>null</code> * <code>false</code> otherwise */ public boolean isNull(String name) { return data.isNull(name); } /** * Merges the content of <code>other</code> into this * document overwriting any mapping for wich other contains a key. * Note that other is copied before merging. * * @param other {@link com.baasbox.android.json.JsonObject} * @return this document with <code>other</code> mappings merged in */ public BaasDocument merge(JsonObject other) { JsonObject o = checkObject(other); data.merge(o); return this; } /** * Associate <code>name</code> key to the {@link com.baasbox.android.json.JsonArray} <code>value</code> * in this document. * * @param name a non <code>null</code> key * @param value a {@link com.baasbox.android.json.JsonArray} * @return this document with the new mapping created */ public BaasDocument put(String name, JsonArray value) { data.put(checkKey(name), value); return this; } private static String checkKey(String key) { if (key == null || key.length() == 0) throw new IllegalArgumentException("key cannot be empty"); if ("id".equals(key)) throw new IllegalArgumentException("key 'id' is reserved"); char f = key.charAt(0); if (f == '@' || f == '_') throw new IllegalArgumentException("key names starting with '_' or '@' are reserved"); return key; } /** * Associate <code>name</code> key to the <code>byte[]</code> <code>value</code> * in this document. * Note that binary data is encoded using base64 and added as strings in the object. * * @param name a non <code>null</code> key * @param value a <code>byte[]</code> array * @return this document with the new mapping created */ public BaasDocument put(String name, byte[] value) { data.put(checkKey(name), value); return this; } /** * Associate <code>name</code> key to the <code>boolean</code> <code>value</code> * in this document. * * @param name a non <code>null</code> key * @param value a <code>boolean</code> value * @return this document with the new mapping created */ public BaasDocument put(String name, boolean value) { data.put(checkKey(name), value); return this; } /** * Associate <code>name</code> key to the <code>double</code> <code>value</code> * in this document. * * @param name a non <code>null</code> key * @param value a <code>double</code> value * @return this document with the new mapping created */ public BaasDocument put(String name, double value) { data.put(checkKey(name), value); return this; } /** * Associate <code>name</code> key to the <code>long</code> <code>value</code> * in this document. * * @param name a non <code>null</code> key * @param value a <code>long</code> value * @return this document with the new mapping created */ public BaasDocument put(String name, long value) { data.put(checkKey(name), value); return this; } /** * Puts an explicit mapping to from <code>name</code> to <code>null</code> * in this document. * <p/> * This is different from not having the mapping at all, to completely remove * the mapping use instead {@link com.baasbox.android.BaasDocument#remove(String)} * * @param name a non <code>null</code> key * @return this document with the new mapping created * @see com.baasbox.android.BaasDocument#remove(String) */ public BaasDocument putNull(String name) { data.putNull(checkKey(name)); return this; } /** * Associate <code>name</code> key to the {@link com.baasbox.android.json.JsonObject} <code>value</code> * in this document. * * @param name a non <code>null</code> key * @param value a {@link com.baasbox.android.json.JsonObject} * @return this document with the new mapping created */ public BaasDocument put(String name, JsonObject value) { data.put(checkKey(name), value); return this; } /** * Associate <code>name</code> key to the {@link java.lang.String} <code>value</code> * in this document. * * @param name a non <code>null</code> key * @param value a {@link java.lang.String} * @return this document with the new mapping created */ public BaasDocument put(String name, String value) { data.put(checkKey(name), value); return this; } // /** // * Associate <code>name</code> key to the {@link com.baasbox.android.json.JsonStructure} <code>value</code> // * in this document. // * // * @param name a non <code>null</code> key // * @param value a {@link com.baasbox.android.json.JsonStructure} // * @return this document with the new mapping created // * @see com.baasbox.android.BaasDocument#put(String, com.baasbox.android.json.JsonArray) // * @see com.baasbox.android.BaasDocument#put(String, com.baasbox.android.json.JsonObject) // */ // // public BaasDocument put(String name, JsonStructure value) { // data.put(checkKey(name), value); // return this; // } /** * Asynchronously refresh the content of this document. * * @param handler a callback to be invoked with the result of the request * @return a {@link com.baasbox.android.RequestToken} to handle the asynchronous request * @throws java.lang.IllegalStateException if this document has no id */ public RequestToken refresh(BaasHandler<BaasDocument> handler) { return refresh(RequestOptions.DEFAULT, handler); } /** * Removes the mapping with <code>name</code> key from the document. * * @param name a non <code>null</code> key * @return the value that was mapped to <code>name</code> if present or <code>null</code> */ public Object remove(String name) { return data.remove(name); } @Override public RequestToken revoke(Grant grant, String username, int flags, BaasHandler<Void> handler) { BaasBox box = BaasBox.getDefaultChecked(); Access access = new Access(box, false, false, collection, id, username, grant, flags, handler); return box.submitAsync(access); } @Override public RequestToken revokeAll(Grant grant, String role, int flags, BaasHandler<Void> handler) { BaasBox box = BaasBox.getDefaultChecked(); Access access = new Access(box, false, true, collection, id, role, grant, flags, handler); return box.submitAsync(access); } @Override public BaasResult<Void> revokeAllSync(Grant grant, String role) { BaasBox box = BaasBox.getDefaultChecked(); Access access = new Access(box, false, true, collection, id, role, grant, RequestOptions.DEFAULT, null); return box.submitSync(access); } @Override public BaasResult<Void> revokeSync(Grant grant, String username) { BaasBox box = BaasBox.getDefaultChecked(); Access access = new Access(box, false, false, collection, id, username, grant, RequestOptions.DEFAULT, null); return box.submitSync(access); } public RequestToken save(BaasHandler<BaasDocument> handler) { return save(SaveMode.IGNORE_VERSION, RequestOptions.DEFAULT, handler); } public RequestToken save(SaveMode mode, BaasHandler<BaasDocument> handler) { return save(mode, RequestOptions.DEFAULT, handler); } public BaasResult<BaasDocument> saveSync() { return saveSync(SaveMode.IGNORE_VERSION); } /** * Returns the number of mappings contained in this document. * * @return the number of mappings contained in this document. */ public int size() { return data.size(); } void update(JsonObject data) { if (!this.collection.equals(data.getString("@class"))) { throw new IllegalStateException("cannot update a document from a different collection than " + this.collection + ": was " + data.getString("@class", "")); } data.remove("@class"); this.id = data.getString("id"); data.remove("id"); this.author = data.getString("_author"); data.remove("_author"); this.creation_date = data.getString("_creation_date"); data.remove("_creation_date"); this.version = data.getLong("@version"); data.remove("@version"); this.rid = data.getString("@rid"); data.remove("@rid"); this.data.merge(data); this.data.setDirty(false); } @Override public boolean isDirty() { return data.isDirty(); } /** * Returns a {@link com.baasbox.android.json.JsonArray} representation * of the values contained in this document. * * @return a {@link com.baasbox.android.json.JsonArray} representation * of the values */ public JsonArray values() { return data.values(); } // -------------------------- INNER CLASSES -------------------------- private static final class Delete extends NetworkTask<Void> { private final BaasDocument document; private final String id; private final String collection; protected Delete(BaasBox box, String collection, String id, int flags, BaasHandler<Void> handler) { super(box, flags, handler); this.document = null; this.collection = collection; this.id = id; } protected Delete(BaasBox box, BaasDocument document, int flags, BaasHandler<Void> handler) { super(box, flags, handler); this.document = document; this.collection = document.collection; this.id = document.id; } @Override protected Void onOk(int status, HttpResponse response, BaasBox box) throws BaasException { if (document != null) document.id = null; return null; } @Override protected Void onSkipRequest() throws BaasException { throw new BaasException("document is not bound to an instance on the server"); } @Override protected HttpRequest request(BaasBox box) { if (id == null) { return null; } else { String endpoint = box.requestFactory.getEndpoint("document/{}/{}", collection, id); return box.requestFactory.delete(endpoint); } } } private static final class Save extends NetworkTask<BaasDocument> { private final BaasDocument document; private final SaveMode mode; private JsonObject data; protected Save(BaasBox box, SaveMode mode, BaasDocument document, int flags, BaasHandler<BaasDocument> handler) { super(box, flags, handler); this.document = document; this.data = document.data.copy(); this.mode = mode; } @Override protected BaasDocument onOk(int status, HttpResponse response, BaasBox box) throws BaasException { JsonObject jsonData = parseJson(response, box).getObject("data"); document.update(jsonData); return document; } @Override protected HttpRequest request(BaasBox box) { String coll = document.collection; String docId = document.id; if (docId == null) { String endpoint = box.requestFactory.getEndpoint("document/{}", coll); return box.requestFactory.post(endpoint, data); } else { String endpoint = box.requestFactory.getEndpoint("document/{}/{}", coll, docId); if (mode == SaveMode.CHECK_VERSION) { data.put("@version", document.version); } return box.requestFactory.put(endpoint, data); } } } private static final class Access extends BaasObject.Access { protected Access(BaasBox box, boolean add, boolean isRole, String collection, String id, String to, Grant grant, int flags, BaasHandler<Void> handler) { super(box, add, isRole, collection, id, to, grant, flags, handler); } @Override protected String userGrant(RequestFactory factory, Grant grant, String collection, String id, String to) { return factory.getEndpoint("document/{}/{}/{}/user/{}", collection, id, grant.action, to); } @Override protected String roleGrant(RequestFactory factory, Grant grant, String collection, String id, String to) { return factory.getEndpoint("document/{}/{}/{}/role/{}", collection, id, grant.action, to); } } private static final class Refresh extends NetworkTask<BaasDocument> { private final BaasDocument document; protected Refresh(BaasBox box, BaasDocument doc, int flags, BaasHandler<BaasDocument> handler) { super(box, flags, handler); this.document = doc; } @Override protected BaasDocument onOk(int status, HttpResponse response, BaasBox box) throws BaasException { JsonObject object = parseJson(response, box).getObject("data"); document.update(object); return document; } @Override protected HttpRequest request(BaasBox box) { String endpoint = box.requestFactory.getEndpoint("document/{}/{}", document.getCollection(), document.getId()); return box.requestFactory.get(endpoint); } } private static final class Fetch extends NetworkTask<List<BaasDocument>> { private final String collection; private final RequestFactory.Param[] filter; protected Fetch(BaasBox box, String collection, BaasQuery.Criteria filter, int flags, BaasHandler<List<BaasDocument>> handler) { super(box, flags, handler); this.collection = collection; this.filter = filter == null ? null : filter.toParams(); } @Override protected List<BaasDocument> onOk(int status, HttpResponse response, BaasBox box) throws BaasException { JsonArray jsonData = parseJson(response, box).getArray("data"); Logger.debug("received: " + jsonData); if (jsonData == null) { return Collections.emptyList(); } else { List<BaasDocument> res = new ArrayList<BaasDocument>(); for (Object obj : jsonData) { res.add(new BaasDocument((JsonObject) obj)); } return res; } } @Override protected HttpRequest request(BaasBox box) { String ep = box.requestFactory.getEndpoint("document/{}", collection); if (filter == null) { return box.requestFactory.get(ep); } else { return box.requestFactory.get(ep, filter); } } } private static final class Count extends NetworkTask<Long> { private final String collection; private final RequestFactory.Param[] params; protected Count(BaasBox box, String collection, BaasQuery.Criteria filter, int flags, BaasHandler<Long> handler) { super(box, flags, handler); this.collection = collection; this.params = filter == null ? null : filter.toParams(); } @Override protected Long onOk(int status, HttpResponse response, BaasBox box) throws BaasException { return parseJson(response, box).getObject("data").getLong("count"); } @Override protected HttpRequest request(BaasBox box) { String ep = box.requestFactory.getEndpoint("document/{}/count", collection); if (params != null) { return box.requestFactory.get(ep, params); } else { return box.requestFactory.get(ep); } } } }