All Downloads are FREE. Search and download functionalities are using the official Maven repository.

co.easimart.CachedCurrentUserController Maven / Gradle / Ivy

package co.easimart;

import java.util.Arrays;
import java.util.Map;

import bolts.Continuation;
import bolts.Task;

/** package */ class CachedCurrentUserController implements EasimartCurrentUserController {

  /**
   * Lock used to synchronize current user modifications and access.
   *
   * Note about lock ordering:
   *
   * You must NOT acquire the EasimartUser instance mutex (the "mutex" field in EasimartObject) while
   * holding this static initialization lock. Doing so will cause a deadlock. Here's an example:
   * https://phabricator.fb.com/P17182641
   */
  private final Object mutex = new Object();
  private final TaskQueue taskQueue = new TaskQueue();

  private final EasimartObjectStore store;

  /* package */ EasimartUser currentUser;
  // Whether currentUser is known to match the serialized version on disk. This is useful for saving
  // a filesystem check if you try to load currentUser frequently while there is none on disk.
  /* package */ boolean currentUserMatchesDisk = false;

  public CachedCurrentUserController(EasimartObjectStore store) {
    this.store = store;
  }

  @Override
  public Task setAsync(final EasimartUser user) {
    return taskQueue.enqueue(new Continuation>() {
      @Override
      public Task then(Task toAwait) throws Exception {
        return toAwait.continueWithTask(new Continuation>() {
          @Override
          public Task then(Task task) throws Exception {
            EasimartUser oldCurrentUser;
            synchronized (mutex) {
              oldCurrentUser = currentUser;
            }

            if (oldCurrentUser != null && oldCurrentUser != user) {
              // We don't need to revoke the token since we're not explicitly calling logOut
              // We don't need to remove persisted files since we're overwriting them
              return oldCurrentUser.logOutAsync(false).continueWith(new Continuation() {
                @Override
                public Void then(Task task) throws Exception {
                  return null; // ignore errors
                }
              });
            }
            return task;
          }
        }).onSuccessTask(new Continuation>() {
          @Override
          public Task then(Task task) throws Exception {
            user.setIsCurrentUser(true);
            return user.synchronizeAllAuthDataAsync();
          }
        }).onSuccessTask(new Continuation>() {
          @Override
          public Task then(Task task) throws Exception {
            return store.setAsync(user).continueWith(new Continuation() {
              @Override
              public Void then(Task task) throws Exception {
                synchronized (mutex) {
                  currentUserMatchesDisk = !task.isFaulted();
                  currentUser = user;
                }
                return null;
              }
            });
          }
        });
      }
    });
  }

  @Override
  public Task setIfNeededAsync(EasimartUser user) {
    synchronized (mutex) {
      if (!user.isCurrentUser() || currentUserMatchesDisk) {
        return Task.forResult(null);
      }
    }

    return setAsync(user);
  }

  @Override
  public Task getAsync() {
    return getAsync(EasimartUser.isAutomaticUserEnabled());
  }

  @Override
  public Task existsAsync() {
    synchronized (mutex) {
      if (currentUser != null) {
        return Task.forResult(true);
      }
    }

    return taskQueue.enqueue(new Continuation>() {
      @Override
      public Task then(Task toAwait) throws Exception {
        return toAwait.continueWithTask(new Continuation>() {
          @Override
          public Task then(Task task) throws Exception {
            return store.existsAsync();
          }
        });
      }
    });
  }

  @Override
  public boolean isCurrent(EasimartUser user) {
    synchronized (mutex) {
      return currentUser == user;
    }
  }

  @Override
  public void clearFromMemory() {
    synchronized (mutex) {
      currentUser = null;
      currentUserMatchesDisk = false;
    }
  }

  @Override
  public void clearFromDisk() {
    synchronized (mutex) {
      currentUser = null;
      currentUserMatchesDisk = false;
    }
    try {
      EasimartTaskUtils.wait(store.deleteAsync());
    } catch (EasimartException e) {
      // ignored
    }
  }

  @Override
  public Task getCurrentSessionTokenAsync() {
    return getAsync(false).onSuccess(new Continuation() {
      @Override
      public String then(Task task) throws Exception {
        EasimartUser user = task.getResult();
        return user != null ? user.getSessionToken() : null;
      }
    });
  }

  @Override
  public Task logOutAsync() {
    return taskQueue.enqueue(new Continuation>() {
      @Override
      public Task then(Task toAwait) throws Exception {
        // We can parallelize disk and network work, but only after we restore the current user from
        // disk.
        final Task userTask = getAsync(false);
        return Task.whenAll(Arrays.asList(userTask, toAwait)).continueWithTask(new Continuation>() {
          @Override
          public Task then(Task task) throws Exception {
            Task logOutTask = userTask.onSuccessTask(new Continuation>() {
              @Override
              public Task then(Task task) throws Exception {
                EasimartUser user = task.getResult();
                if (user == null) {
                  return task.cast();
                }
                return user.logOutAsync();
              }
            });

            Task diskTask = store.deleteAsync().continueWith(new Continuation() {
              @Override
              public Void then(Task task) throws Exception {
                boolean deleted = !task.isFaulted();
                synchronized (mutex) {
                  currentUserMatchesDisk = deleted;
                  currentUser = null;
                }
                return null;
              }
            });
            return Task.whenAll(Arrays.asList(logOutTask, diskTask));
          }
        });
      }
    });
  }

  @Override
  public Task getAsync(final boolean shouldAutoCreateUser) {
    synchronized (mutex) {
      if (currentUser != null) {
        return Task.forResult(currentUser);
      }
    }

    return taskQueue.enqueue(new Continuation>() {
      @Override
      public Task then(Task toAwait) throws Exception {
        return toAwait.continueWithTask(new Continuation>() {
          @Override
          public Task then(Task ignored) throws Exception {
            EasimartUser current;
            boolean matchesDisk;
            synchronized (mutex) {
              current = currentUser;
              matchesDisk = currentUserMatchesDisk;
            }

            if (current != null) {
              return Task.forResult(current);
            }

            if (matchesDisk) {
              if (shouldAutoCreateUser) {
                return Task.forResult(lazyLogIn());
              }
              return null;
            }

            return store.getAsync().continueWith(new Continuation() {
              @Override
              public EasimartUser then(Task task) throws Exception {
                EasimartUser current = task.getResult();
                boolean matchesDisk = !task.isFaulted();

                synchronized (mutex) {
                  currentUser = current;
                  currentUserMatchesDisk = matchesDisk;
                }

                if (current != null) {
                  synchronized (current.mutex) {
                    current.setIsCurrentUser(true);
                  }
                  return current;
                }

                if (shouldAutoCreateUser) {
                  return lazyLogIn();
                }
                return null;
              }
            });
          }
        });
      }
    });
  }

  private EasimartUser lazyLogIn() {
    Map authData = EasimartAnonymousUtils.getAuthData();
    return lazyLogIn(EasimartAnonymousUtils.AUTH_TYPE, authData);
  }

  /* package for tests */ EasimartUser lazyLogIn(String authType, Map authData) {
    // Note: if authType != EasimartAnonymousUtils.AUTH_TYPE the user is not "lazy".
    EasimartUser user = EasimartObject.create(EasimartUser.class);
    synchronized (user.mutex) {
      user.setIsCurrentUser(true);
      user.putAuthData(authType, authData);
    }

    synchronized (mutex) {
      currentUserMatchesDisk = false;
      currentUser = user;
    }

    return user;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy