Source: lib/EntityManagerFactory.js

'use strict';

const message = require('./message');
const metamodel = require('./metamodel');

const util = require('./util');
const deprecated = require('./util/deprecated');
const Connector = require('./connector/Connector');
const EntityManager = require('./EntityManager');

const CONNECTED = Symbol('Connected');

/**
 * @alias EntityManagerFactory
 * @extends util.Lockable
 */
class EntityManagerFactory extends util.Lockable {
  /**
   * Creates a new EntityManagerFactory connected to the given destination
   * @param {string|Object} [options] The destination to connect with, or an options object
   * @param {string} [options.host] The destination to connect with
   * @param {number} [options.port=80|443] The optional destination port to connect with
   * @param {boolean} [options.secure=false] <code>true</code> To use a secure ssl encrypted connection
   * @param {string} [options.basePath="/v1"] The base path of the api
   * @param {Object} [options.schema=null] The serialized schema as json used to initialize the metamodel
   * @param {util.TokenStorage} [options.tokenStorage] The tokenStorage which should be used by this emf
   * @param {util.TokenStorageFactory} [options.tokenStorageFactory] The tokenStorage factory implementation which
   * should be used for token storage
   * @param {number} [options.staleness=60] The maximum staleness of objects that are acceptable while reading cached
   * data
   */
  constructor(options) {
    super();

    const opt = Object(options) instanceof String ? { host: options } : options || {};

    /** @type connector.Connector */
    this.connection = null;
    /** @type metamodel.Metamodel */
    this.metamodel = this.createMetamodel();
    /** @type util.Code */
    this.code = new util.Code(this.metamodel, this);
    /** @type util.TokenStorageFactory */
    this.tokenStorageFactory = util.TokenStorage.WEB_STORAGE || util.TokenStorage.GLOBAL;

    this.configure(opt);

    let isReady = true;
    let ready = new Promise((success) => {
      this[CONNECTED] = success;
    });

    if (opt.host) {
      this.connect(opt.host, opt.port, opt.secure, opt.basePath);
    } else {
      isReady = false;
    }

    if (!this.tokenStorage) {
      isReady = false;
      ready = ready
        .then(() => this.tokenStorageFactory.create(this.connection.origin))
        .then((tokenStorage) => {
          this.tokenStorage = tokenStorage;
        });
    }

    if (opt.schema) {
      this.connectData = opt;
      this.metamodel.init(opt.schema);
    } else {
      isReady = false;
      ready = ready.then(() => {
        const msg = new message.Connect();
        msg.withCredentials = true; // used for registered devices

        if (this.staleness === 0) {
          msg.noCache();
        }

        return this.send(msg);
      }).then((response) => {
        this.connectData = response.entity;

        if (this.staleness === undefined) {
          this.staleness = this.connectData.bloomFilterRefresh || 60;
        }

        if (!this.metamodel.isInitialized) {
          this.metamodel.init(this.connectData.schema);
        }

        this.tokenStorage.update(this.connectData.token);
      });
    }

    if (!isReady) {
      this.withLock(() => ready, true);
    }
  }

  /**
   * Apply additional configurations to this EntityManagerFactory
   * @param {Object} options The additional configuration options
   * @param {util.TokenStorage} [options.tokenStorage] The tokenStorage which should be used by this emf
   * @param {util.TokenStorageFactory} [options.tokenStorageFactory] The tokenStorage factory implementation which
   * should be used for token storage
   * @param {number} [options.staleness=60] The maximum staleness of objects that are acceptable while reading cached
   * data, <code>0</code> to always bypass the browser cache
   * @return {void}
   */
  configure(options) {
    if (this.connection) {
      throw new Error('The EntityManagerFactory can only be configured before is is connected.');
    }

    if (options.tokenStorage) {
      /** @type util.TokenStorage */
      this.tokenStorage = options.tokenStorage;
    }

    if (options.tokenStorageFactory) {
      this.tokenStorageFactory = options.tokenStorageFactory;
    }

    if (options.staleness !== undefined) {
      /** @type number */
      this.staleness = options.staleness;
    }
  }

  /**
   * Connects this EntityManager to the given destination
   * @param {string} hostOrApp The host or the app name to connect with
   * @param {number} [port=80|443] The port to connect to
   * @param {boolean} [secure=false] <code>true</code> To use a secure connection
   * @param {string} [basePath="/v1"] The base path of the api
   * @return {Promise<this>}
   */
  connect(hostOrApp, port, secure, basePath) {
    if (this.connection) {
      throw new Error('The EntityManagerFactory is already connected.');
    }

    if (Object(port) instanceof Boolean) {
      return this.connect(hostOrApp, 0, port, secure);
    }

    this.connection = Connector.create(hostOrApp, port, secure, basePath);

    this[CONNECTED]();
    return this.ready();
  }

  /**
   * Connects this EntityManager to the given destination
   * @param {string} hostOrApp The host or the app name to connect with
   * @param {boolean} [secure=false] <code>true</code> To use a secure connection
   * @return {Promise<this>}
   * @name connect
   * @memberOf EntityManagerFactory.prototype
   * @method
   */


  /**
   * Creates a new Metamodel instance, which is not connected
   * @return {metamodel.Metamodel} A new Metamodel instance
   */
  createMetamodel() {
    return new metamodel.Metamodel(this);
  }

  /**
   * Create a new application-managed EntityManager.
   *
   * @param {boolean=} useSharedTokenStorage The token storage to persist the authorization token, or
   * <code>true</code> To use the shared token storage of the emf.
   * <code>false</code> To use a instance based storage.
   *
   * @return {EntityManager} a new entityManager
   */
  createEntityManager(useSharedTokenStorage) {
    const em = new EntityManager(this);

    if (this.isReady) {
      em.connected(
        this.connection,
        this.connectData,
        useSharedTokenStorage ? this.tokenStorage : new util.TokenStorage(this.connection.origin)
      );
    } else {
      em.withLock(() => this.ready().then(() => {
        em.connected(
          this.connection,
          this.connectData,
          useSharedTokenStorage ? this.tokenStorage : new util.TokenStorage(this.connection.origin)
        );
      }), true);
    }

    return em;
  }

  send(msg) {
    if (!msg.tokenStorage) {
      msg.tokenStorage = this.tokenStorage;
    }
    return this.connection.send(msg);
  }
}

deprecated(EntityManagerFactory.prototype, '_connector', 'connection');
deprecated(EntityManagerFactory.prototype, '_connected', CONNECTED);

module.exports = EntityManagerFactory;