com.cloudmine.api.CMObject.java Source code

Java tutorial

Introduction

Here is the source code for com.cloudmine.api.CMObject.java

Source

package com.cloudmine.api;

import com.cloudmine.api.exceptions.AccessException;
import com.cloudmine.api.exceptions.ConversionException;
import com.cloudmine.api.exceptions.CreationException;
import com.cloudmine.api.rest.CMStore;
import com.cloudmine.api.rest.JsonUtilities;
import com.cloudmine.api.rest.Savable;
import com.cloudmine.api.rest.Transportable;
import com.cloudmine.api.rest.TransportableString;
import com.cloudmine.api.rest.callbacks.CMCallback;
import com.cloudmine.api.rest.callbacks.Callback;
import com.cloudmine.api.rest.callbacks.CreationResponseCallback;
import com.cloudmine.api.rest.response.CreationResponse;
import com.cloudmine.api.rest.response.ObjectModificationResponse;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * Can be subclassed to allow for persisting POJOs to CloudMine. If you would like to specify a custom class name
 * (for example, for interoperability with existing iOS CMObjects), you may override getClassName(). If you do this,
 * you must also call {@link com.cloudmine.api.persistance.ClassNameRegistry#register(String, Class)} in the same place
 * that you call {@link CMApiCredentials.initialize(String, String, Object)}
 * <br>
 * Copyright CloudMine LLC. All rights reserved<br>
 * See LICENSE file included with SDK for details.
 */
public class CMObject implements Transportable, Savable<ObjectModificationResponse, ObjectModificationResponse> {
    //******SEE CMApiCredentials for static declaration of Class mapping*******
    private static final Logger LOG = LoggerFactory.getLogger(CMObject.class);
    public static final String MISSING_OBJECT_ID = "";
    public static final String ACCESS_KEY = "__access__";

    private String objectId;
    private Immutable<StoreIdentifier> storeId = new Immutable<StoreIdentifier>();
    private Set<String> accessListIds = new HashSet<String>();

    public static <CMO extends CMObject> Transportable massTransportable(Collection<CMO> objects) {
        StringBuilder bodyBuilder = new StringBuilder("{");
        String separator = "";
        for (CMObject object : objects) {
            bodyBuilder.append(separator).append(object.asKeyedObject());
            separator = ", ";
        }
        bodyBuilder.append("}");
        return new TransportableString(bodyBuilder.toString());
    }

    /**
     * Converts the given TransportableRepresentation to an object of the given class
     * @param transportableRepresentation
     * @param objectClass
     * @param <T>
     * @return
     * @throws ConversionException
     */
    public static <T extends CMObject> T convertTransportableRepresentationToObject(
            String transportableRepresentation, Class<T> objectClass) throws ConversionException {
        return JsonUtilities.jsonToClass(transportableRepresentation, objectClass);
    }

    /**
     * Like {@link #convertTransportableCollectionToObjectMap} but untyped; should be used when you don't know the type,
     * or when you have a collection of multiple types.
     * @param transportableRepresentations
     * @return
     */
    public static Map<String, CMObject> convertTransportableCollectionToObjectMap(
            String transportableRepresentations) {
        return JsonUtilities.jsonToClassMap(transportableRepresentations);
    }

    /**
     * Convert a transportable representation of a collection of objects keyed by their id's, to a Map of those keys to the objects
     * @param transportableCollection a transportable representation containing only objects of type T
     * @param objectClass the class of objects contained within the Transportable representation
     * @param <T> the type of the objects contained in transportableCollection
     * @return a Map from object ids to objects of type T
     * @throws ConversionException  if unable to convert, either because the given representation is of a different class, or because it is malformed.
     */
    public static <T extends CMObject> Map<String, T> convertTransportableCollectionToObjectMap(
            String transportableCollection, Class<T> objectClass) throws ConversionException {
        return JsonUtilities.jsonToClassMap(transportableCollection, objectClass);
    }

    protected static String generateUniqueObjectId() {
        return UUID.randomUUID().toString();
    }

    /**
     * Create a new CMObject with a randomly generated object id
     */
    public CMObject() {
        this(generateUniqueObjectId());
    }

    /**
     * Create a new CMObject with a specific object id
     * @param objectId a non null object id for this CMObject
     * @throws NullPointerException if given a null objectid
     */
    public CMObject(String objectId) throws NullPointerException {
        this(objectId, true);
    }

    protected CMObject(String objectId, boolean hasObjectid) {
        if (objectId == null && hasObjectid)
            throw new NullPointerException("Cannot have a null objectId");
        if (hasObjectid) {
            this.objectId = objectId;
        }
    }

    /**
     * Create a new CMObject that does not have an object id. This is useful for subobjects
     * @param autogenerateObjectId if true, equivalent to {@link #CMObject()}, otherwise have no object id
     */
    public CMObject(boolean autogenerateObjectId) {
        if (autogenerateObjectId)
            this.objectId = generateUniqueObjectId();
    }

    @Override
    public String transportableRepresentation() throws ConversionException {
        return JsonUtilities.cmobjectsToJson(this);
    }

    /**
     * Get a representation of this object in the form "objectId":{contents}
     * @return a representation of this object in the form "objectId":{contents}
     * @throws ConversionException if this object cannot be converted to transportable
     */
    public String asKeyedObject() throws ConversionException {
        return JsonUtilities.unwrap(transportableRepresentation());
    }

    @JsonIgnore
    @Deprecated
    public boolean setSaveWith(StoreIdentifier identifier) {
        LOG.debug("StoreId is current: " + storeId + " and if unset will be set to " + identifier);
        return storeId.setValue(identifier);
    }

    /**
     * Set that this object should be saved at the User level, and should be saved using the given CMSessionToken.
     * @param user the user to save this CMObject with
     * @return true if the value was set; false if it has already been set OR null was passed in
     */
    @JsonIgnore
    @Deprecated
    public boolean setSaveWith(JavaCMUser user) {
        try {
            return setSaveWith(StoreIdentifier.StoreIdentifier(user));
        } catch (CreationException e) {
            LOG.error("CreationException thrown, setSaveWith not set", e);
            return false;
        }
    }

    /**
     * Gets the StoreIdentifier which defines where this CMObject will be saved. If it has not yet been set, {@link StoreIdentifier#DEFAULT} is returned
     * @return the StoreIdentifier which defines where this CMObject will be saved. If it has not yet been set, {@link StoreIdentifier#DEFAULT} is returned
     */
    @JsonIgnore
    @Deprecated
    public StoreIdentifier getSavedWith() {
        return storeId.value(StoreIdentifier.DEFAULT);
    }

    /**
     * Check whether this CMObject saves to a particular level
     * @param level the level to check
     * @return true if this saves with the given level
     */
    @Deprecated
    public boolean isOnLevel(ObjectLevel level) {
        return getSavedWith().isLevel(level);
    }

    @Override
    @JsonIgnore
    @Deprecated
    public boolean isUserLevel() {
        return isOnLevel(ObjectLevel.USER);
    }

    @Override
    @JsonIgnore
    @Deprecated
    public boolean isApplicationLevel() {
        return isOnLevel(ObjectLevel.APPLICATION);
    }

    /**
     * Allow the user's associated with the given list access to this object. The access they get depends on the
     * permissions defined by the list. The given list must have an object id
     * @param list
     */
    public void grantAccess(JavaAccessListController list) {
        if (list == null)
            return;
        addAccessListId(list.getObjectId());
    }

    public void addAccessListId(String listId) {
        accessListIds.add(listId);
    }

    public void setAccessListIds(Set<String> accessListIds) {
        this.accessListIds = accessListIds;
    }

    @JsonProperty(ACCESS_KEY)
    @JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY)
    public Set<String> getAccessListIds() {
        return accessListIds;
    }

    /**
     * Save this object in its associated store; if you have not specified this with {@link #setSaveWith(StoreIdentifier)}
     * then it saves to the APPLICATION store. Once a CMObject has been saved, it cannot be saved to a
     * different
     * @throws ConversionException if unable to convert to transportable representation; this should not happen unless you are subclassing this and doing something you shouldn't be with overriding transportableRepresentation
     * @throws CreationException if CMApiCredentials has not been initialized properly
     */
    public void save() throws ConversionException, CreationException {
        save(CMCallback.<ObjectModificationResponse>doNothing());
    }

    /**
     * Save this object in its associated store; if you have not specified this with {@link #setSaveWith(StoreIdentifier)}
     * then it saves to the APPLICATION store. Once a CMObject has been saved, it cannot be saved to a
     * different
     * @throws ConversionException if unable to convert to transportable representation; this should not happen unless you are subclassing this and doing something you shouldn't be with overriding transportableRepresentation
     * @throws CreationException if CMApiCredentials has not been initialized properly
     */
    public void save(Callback<ObjectModificationResponse> callback) throws CreationException, ConversionException {
        store().saveObject(this, callback);
    }

    public void saveWithUser(JavaCMUser user) throws CreationException, ConversionException {
        saveWithUser(user, CMCallback.<ObjectModificationResponse>doNothing());
    }

    /**
     * Save this object in in the given CMUser's store. If {@link #setSaveWith(StoreIdentifier)} has already been called
     * Once a CMObject has been saved, it cannot be saved to a
     * different
     * @param user the user to save this object with
     * @param callback a Callback that expects an ObjectModificationResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in for this
     * @throws ConversionException if unable to convert to transportable representation; this should not happen unless you are subclassing this and doing something you shouldn't be with overriding transportableRepresentation
     * @throws CreationException if CMApiCredentials has not been initialized properly
     * @throws com.cloudmine.api.exceptions.AccessException if setSaveWith has already been set
     */
    public void saveWithUser(JavaCMUser user, Callback<ObjectModificationResponse> callback)
            throws CreationException, AccessException, ConversionException {
        boolean wasAlreadySet = !setSaveWith(user);
        boolean notSameUser = wasAlreadySet && //skip the check if it wasn't already set; still check below in if statement for clarity
                !user.equals(getUser());
        if (wasAlreadySet && notSameUser) {
            throw new AccessException("Cannot save with user if saveWith has already been set");
        }
        save(callback);
    }

    /**
     * See {@link #delete(com.cloudmine.api.rest.callbacks.Callback)}
     */
    public void delete() {
        delete(CMCallback.<ObjectModificationResponse>doNothing());
    }

    /**
     * Delete this object, then run the given callback
     * @param callback a Callback that expects an ObjectModificationResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in for this
     */
    public void delete(Callback<ObjectModificationResponse> callback) {
        store().deleteObject(this, callback);
    }

    /**
     * This method should be used to check date equality when overriding {@link #equals(Object)}, as
     * serialized dates are stored in seconds.
     * @param firstDate a null possible date
     * @param secondDate a null possible date
     * @return true if the two dates represent the same second in time
     */
    public static boolean dateEquals(Date firstDate, Date secondDate) {
        if ((firstDate == null && secondDate != null) || (firstDate != null && secondDate == null)) {
            return false;
        }
        if (firstDate == null && secondDate == null) {
            return true;
        }

        int firstSeconds = firstDate.getSeconds();
        int secondSeconds = secondDate.getSeconds();
        return firstSeconds == secondSeconds;
    }

    @Override
    @JsonIgnore
    public JavaCMUser getUser() {
        return getSavedWith().getUser();
    }

    @Override
    @JsonProperty(JsonUtilities.OBJECT_ID_KEY)
    /**
     * Get the objectId for this CMObject. In certain cases this may not be set; for example, CMUsers do not have an
     * object id until they have been persisted. In this case, {@link #MISSING_OBJECT_ID} is returned. You can also
     * check for the existence of an objectId by calling {@link #hasObjectId()}
     * @return The unique objectId for this object, or {@link #MISSING_OBJECT_ID} if {@link #hasObjectId()} returns false
     */
    public String getObjectId() {
        return objectId == null ? MISSING_OBJECT_ID : objectId;
    }

    /**
     * Think real hard before using this method. If this object has already been saved, changing the objectId will cause
     * a new copy to be saved. If the objectId is being used as a key in a Map, then changing it will not update the maps.
     * If you change the objectId of an object being managed by the store, if you query the store by objectId it will still
     * be under its old value.
     * Basically, STAY AWAY unless you have a very good reason, or are only calling before the object is used by anything.
     * @param objectId
     */
    public void setObjectId(String objectId) {
        this.objectId = objectId;
    }

    @JsonIgnore
    public boolean hasObjectId() {
        return objectId != null;
    }

    @JsonProperty("__class__")
    public String getClassName() {
        return getClass().getName();
    }

    /**
     * This wraps the given callback in a {@link com.cloudmine.api.rest.callbacks.CreationResponseCallback} that will set this CMUser's object id on
     * success, and then call {@link Callback#onCompletion(Object)} passing in the {@link com.cloudmine.api.rest.response.CreationResponse}
     * You probably don't need to be calling this ever
     * @param callback
     * @return
     */
    public final CreationResponseCallback setObjectIdOnCreation(final Callback<CreationResponse> callback) {
        return new CreationResponseCallback() {
            public void onCompletion(CreationResponse response) {
                try {
                    if (response.wasSuccess()) {
                        setObjectId(response.getObjectId());
                    }
                } finally {
                    callback.onCompletion(response);
                }
            }
        };
    }

    protected CMStore store() throws CreationException {
        return CMStore.getStore(storeId.value(StoreIdentifier.DEFAULT));
    }

    public String toString() {
        try {
            return transportableRepresentation();
        } catch (Exception e) {
            return super.toString();
        }
    }
}