PrototypeContext.js

import { NEW_ID_PLACEHOLDER } from './PrototypeEntry.js'
import Context from './Context.js';

/**
 * A not yet existing context (on the server). The context is accessible from a PrototypeContextEntry.
 * Allows creating initial prototype entries within the context even before the context is created (before commit).
 * When the context is created (via commit on its entry) all the initial entries are created as well (after the context is created).
 * The initial entries are available via the getInitialEntries method.
 *
 * The main functionality of this class are:
 *  1. to wrap all methods for creating entries so they are created in a later stage after the context has been created
 *  2. to rewrite all references between the entries after the context has been created with the right context id
 *  3. allow access to the initial entries after context creation
 *
 * @exports store/PrototypeContext
 */
export default class PrototypeContext extends Context {
  /**
   * @param {string} entryURI - URI to an entry where this resource is contained.
   * @param {string} resourceURI - URI to the resource.
   * @param {EntryStore} entryStore - the API's repository instance.
   */
  // eslint-disable-next-line no-useless-constructor
  constructor(entryURI, resourceURI, entryStore) {
    super(entryURI, resourceURI, entryStore);
    this.entryIdCounter = 0;
    this.id2pe = {};
  }

  /**
   * Creates an identifier if no explicit identifier is provided.
   * Currently the identifiers are created in numerical order.
   *
   * @param id
   * @return {string}
   * @private
   */
  _newId(id) {
    if (this.id2pe.hasOwnProperty(`${id}`)) {
      throw new Error('Id is already taken');
    }
    if (id === undefined) {
      this.entryIdCounter += 1;
      return `${this.entryIdCounter}`;
    } else {
      return `${id}`;
    }
  }

  /**
   * Utility method to simplify overriding of the superclass "new" methods.
   * @param {PrototypeEntry} pe
   * @return {PrototypeEntry}
   * @private
   */
  _register(pe) {
    this.id2pe[pe.getSpecificId()] = pe;
    pe.delayedCommit = pe.commit;
    pe.commit = () => {};
    return pe;
  }

  newEntry(id) {
    return this._register(super.newEntry(this._newId(id)));
  }

  newNamedEntry(id) {
    return this._register(super.newNamedEntry(this._newId(id)));
  }

  newLink(link, id) {
    return this._register(super.newLink(link, this._newId(id)));
  }

  newLinkRef(link, metadataLink, id) {
    return this._register(super.newLinkRef(link, metadataLink, this._newId(id)));
  }

  newRef(link, metadataLink, id) {
    return this._register(super.newRef(link, metadataLink, this._newId(id)));
  }

  newList(id) {
    return this._register(super.newList(this._newId(id)));
  }

  newGraph(graph = {}, id) {
    return this._register(super.newGraph(graph, this._newId(id)));
  }

  newString(str, id) {
    return this._register(super.newString(str, this._newId(id)));
  }

  newPipeline(id) {
    return this._register(super.newPipeline(this._newId(id)));
  }

  /**
   * Call this after the context has been created to update the initial prototype entries
   * so they can be created with the right context identifier.
   *
   * @param {Context} context
   */
  updateEntriesForCreatedContext(context) {
    const es = this.getEntryStore();
    const cru = context.getResourceURI();

    const oldBase = `${es.getBaseURI()}${NEW_ID_PLACEHOLDER}/`;
    const newBase = `${es.getBaseURI()}${context.getId()}/`;
    const uri2uri = {};
    Object.keys(this.id2pe).forEach(eid => {
      uri2uri[`${oldBase}resource/${eid}`] = `${newBase}resource/${eid}`;
      uri2uri[`${oldBase}entry/${eid}`] = `${newBase}entry/${eid}`;
      uri2uri[`${oldBase}metadata/${eid}`] = `${newBase}metadata/${eid}`;
      uri2uri[`${oldBase}cached-external-metadata/${eid}`] = `${newBase}cached-external-metadata/${eid}`;
    });

    Object.keys(this.id2pe).forEach(eid => {
      const pe = this.id2pe[eid];
      pe._context = context;        // Move over the prototype entry to the newly created context
      const ei = pe.getEntryInfo();
      const eidGraph = ei.getGraph();
      const md = pe.getMetadata();
      const cemd = pe.getCachedExternalMetadata();
      ei._entryURI = `${newBase}entry/${eid}`;       // Interal variable that needs to be updated
      Object.keys(uri2uri).forEach((fromURI => {
        const toURI = uri2uri[fromURI];
        md.replaceURI(fromURI, toURI);
        cemd.replaceURI(fromURI, toURI);
        eidGraph.replaceURI(fromURI, toURI);
      }));
    })
  }

  /**
   * Creates all the initial prototype entries in the context.
   *
   * @return {Promise<Entry[]>}
   */
  async createInitialEntries() {
    const pes = Object.values(this.id2pe);
    const proms = pes.map(pe => pe.delayedCommit());
    this._initialEntries = await Promise.all(proms);
    return this._initialEntries;
  }

  /**
   * @return {Entry[]}
   */
  getInitialEntries() {
    return this._initialEntries;
  }
};