Home Manual Reference Source Test

src/lib/server/AddReferencesStream.js

import { DataType } from 'node-opcua/lib/datamodel/variant';
import { StatusCodes } from 'node-opcua/lib/datamodel/opcua_status_code';
import NodeId from '../model/opcua/NodeId';
import { ReferenceTypeIds } from '../model/Node';
import Atviseproject from '../config/Atviseproject';
import CallScriptStream from './scripts/CallScriptStream';
import { waitForDependencies } from './WaitingStream';

const serverNodes = new Set(Atviseproject.ServerRelatedNodes.map((id) => id.value));

const ignoredReferences = new Set([
  ReferenceTypeIds.toParent,
  ReferenceTypeIds.HasTypeDefinition,
  ReferenceTypeIds.HasModellingRule,
]);

/**
 * A stream that adds non-standard references to nodes when pushed.
 */
export default class AddReferencesStream extends waitForDependencies(CallScriptStream) {
  /**
   * Creates a new stream for adding references to pushed nodes.
   * @param {Object} options The options to pass to the {@link CallScriptStream}.
   */
  constructor(options) {
    super(options);

    /**
     * A stack of {@link NodeId#value}s to be retried afterwards.
     * @type {Set<string>}
     */
    this._retry = new Set();
  }

  /**
   * Returns the references that need to be set for a file.
   * @param {AtviseFile} file The file to check.
   * @return {Object} The files's references.
   */
  referencesToAdd(file) {
    const additionalReferences = Object.assign({}, file.references);
    delete additionalReferences.toParent;
    delete additionalReferences.HasTypeDefinition;
    delete additionalReferences.HasModellingRule;

    return additionalReferences;
  }

  /**
   * Returns the referenced nodes that should be processed before the given file.
   * @param {AtviseFile} file The file to check.
   * @return {NodeId[]} The files dependencies.
   */
  dependenciesFor(file) {
    const refs = this.referencesToAdd(file);

    return Object.values(refs)
      .reduce((deps, nodes) => deps.concat(nodes), [])
      .filter(({ value }) => !serverNodes.has(value));
  }

  /**
   * Id of the *CreateNode* script added with `atscm import`.
   * @type {NodeId}
   */
  get scriptId() {
    return new NodeId(
      NodeId.NodeIdType.STRING,
      'SYSTEM.LIBRARY.ATVISE.SERVERSCRIPTS.atscm.AddReferences',
      1
    );
  }

  /**
   * The options required to add references to the node for the given file.
   * @param {AtviseFile} file The processed file.
   * @return {Object} The options passed to the *AddReferences* script.
   */
  scriptParameters(file) {
    const references = [...file.references].reduce((result, [key, value]) => {
      if (ignoredReferences.has(key)) {
        return result;
      }

      return Object.assign(result, {
        [key]: [...value].map((s) => (typeof s === 'string' ? `ns=1;s=${s}` : s)),
      });
    }, {});

    const referenceKeys = Object.keys(references);

    if (referenceKeys.length > 0) {
      return {
        paramObjString: {
          dataType: DataType.String,
          value: JSON.stringify({
            nodeId: file.nodeId,
            references: referenceKeys.map((type) => ({
              referenceIdValue: parseInt(type, 10),
              items: references[type],
            })),
          }),
        },
      };
    }

    // No need to add references
    return null;
  }

  /**
   * Prints an error message telling that adding one or more references failed.
   * @param {AtviseFile} file The file who's node could not be created.
   * @return {string} The resulting error message.
   */
  processErrorMessage(file) {
    return `Error adding references to node ${file.nodeId}`;
  }

  /**
   * Handles the results of a script call.
   * @param {AtviseFile} file The file the script was called with.
   * @param {node-opcua~Variant[]} outArgs The raw method results.
   * @param {function(err: Error)} callback Called once finished.
   */
  handleOutputArguments(file, outArgs, callback) {
    if (outArgs[0].value !== StatusCodes.Good) {
      callback(new Error(outArgs[1].value));
    } else {
      const [{ value: failures }] = outArgs[3].value;

      if (failures && failures.length) {
        const retryKey = file.nodeId.value;

        if (this._retry.has(retryKey)) {
          this._retry.delete(retryKey);
          callback(new Error(`Failed to create references to ${failures.join(', ')}`));
        } else {
          this._retry.add(retryKey);
          callback(null);

          this.once('drained', () => {
            this.write(file);
          });
        }

        return;
      }

      callback(null);
    }
  }
}