docs: function resolveResourceTree(resourceTree, parentDataSourceName) {
      const dataSources = {};
      const primaryName = selectPrimaryDataSource(resourceTree);
  
      if (resourceTree.attrNode) {
          resourceTree.attrNode.primaryDataSource = primaryName;
      }
  
      // determine needed DataSources (simple logic - to be optimized for complex cases):
      dataSources[primaryName] = true;
      resourceTree.attributes.forEach(attrInfo => {
          if (attrInfo.fromDataSource === '#all-selected' || attrInfo.fromDataSource === '#current-primary') return;
  
          // handle key-group (select all attributes of one composite key from same DataSource):
          if (attrInfo.fromDataSource === '#same-group') {
              let selectedDataSource = primaryName;
              const possibleDataSources = Object.keys(attrInfo.subResourceAttrNode.resolvedParentKey);
  
              if (possibleDataSources.indexOf(selectedDataSource) === -1) {
                  // just select first possible one - optimize?
                  selectedDataSource = possibleDataSources[0];
                  attrInfo.subResourceAttrNode.parentDataSource = selectedDataSource;
              }
  
              attrInfo.attributes.forEach(groupAttrInfo => {
                  groupAttrInfo.fromDataSource = selectedDataSource;
              });
  
              dataSources[selectedDataSource] = true;
              return;
          }
  
          dataSources[Object.keys(attrInfo.dataSourceMap)[0]] = true;
      });
  
      // initialize needed DataSources:
      Object.keys(dataSources).forEach(dataSourceName => {
          dataSources[dataSourceName] = Object.assign({}, resourceTree.dataSources[dataSourceName]);
      });
      if (resourceTree.attrNode) {
          resourceTree.attrNode.dataSources = dataSources; // for use in result-builder
      }
  
      resolveDataSourceAttributes(resourceTree, dataSources, primaryName);
      resolveDataSourceOptions(resourceTree, dataSources, primaryName);
  
      let dataSourceTree = {};
      if (resourceTree.attrPath) {
          dataSourceTree.attributePath = resourceTree.attrPath;
          dataSourceTree.dataSourceName = primaryName;
      }
  
      dataSourceTree.request = dataSources[primaryName];
      dataSourceTree.attributeOptions = dataSourceTree.request.attributeOptions;
      delete dataSourceTree.request.attributeOptions;
  
      if (resourceTree.resourceName) dataSourceTree.resourceName = resourceTree.resourceName;
  
      if (parentDataSourceName && resourceTree.parentKey && resourceTree.childKey) {
          if (!resourceTree.parentKey[parentDataSourceName]) {
              throw new ImplementationError(
                  [
                      'Parent key' + (resourceTree.attrPath ? ' of "' + resourceTree.attrPath.join('.') + '"' : ''),
                      'not in "' + parentDataSourceName + '"-DataSource of parent resource'
                  ].join(' ')
              );
          }
  
          dataSourceTree.parentKey = resourceTree.parentKey[parentDataSourceName];
          dataSourceTree.childKey = resourceTree.childKey[primaryName];
          if ('multiValuedParentKey' in resourceTree && 'uniqueChildKey' in resourceTree) {
              dataSourceTree.multiValuedParentKey = resourceTree.multiValuedParentKey;
              dataSourceTree.uniqueChildKey = resourceTree.uniqueChildKey;
          }
      }
  
      // append secondary DataSources:
      const secondaryDataSources = {};
  
      Object.keys(dataSources).forEach(dataSourceName => {
          if (dataSourceName === primaryName) return;
          if (dataSources[dataSourceName].joinParentKey) return;
  
          if (!dataSourceTree.subRequests) {
              dataSourceTree.subRequests = [];
          }
  
          const filter = {
              attribute: resourceTree.primaryKey[dataSourceName],
              operator: 'equal',
              valueFromParentKey: true
          };
  
          if (filter.attribute.length === 1) {
              filter.attribute = filter.attribute[0];
          }
  
          dataSources[dataSourceName].filter = [[filter]];
  
          const subRequest = {
              attributePath: resourceTree.attrPath,
              dataSourceName,
              parentKey: resourceTree.primaryKey[primaryName],
              childKey: resourceTree.primaryKey[dataSourceName],
              multiValuedParentKey: false,
              uniqueChildKey: true,
              request: dataSources[dataSourceName]
          };
  
          if (resourceTree.resourceName) subRequest.resourceName = resourceTree.resourceName;
  
          subRequest.attributeOptions = subRequest.request.attributeOptions;
          delete subRequest.request.attributeOptions;
  
          dataSourceTree.subRequests.push(subRequest);
          // also index by name to resolve dependencies later
          secondaryDataSources[dataSourceName] = subRequest;
      });
  
      if (resourceTree.subFilters) {
          resourceTree.subFilters.forEach(subFilterTree => {
              const subDataSourceTree = resolveResourceTree(subFilterTree, primaryName);
  
              if (!dataSourceTree.subFilters) {
                  dataSourceTree.subFilters = [];
              }
              dataSourceTree.subFilters.push(subDataSourceTree);
          });
      }
  
      if (resourceTree.children) {
          resourceTree.children.forEach(subResourceTree => {
              let parentDataSourceTree = dataSourceTree;
              let dataSourceName = primaryName;
  
              // check if we depend on a secondary DataSource:
              if (
                  subResourceTree.attrNode.parentDataSource &&
                  dataSourceName !== subResourceTree.attrNode.parentDataSource
              ) {
                  dataSourceName = subResourceTree.attrNode.parentDataSource;
                  parentDataSourceTree = secondaryDataSources[dataSourceName];
              }
  
              const subDataSourceTree = resolveResourceTree(subResourceTree, dataSourceName);
  
              if (!parentDataSourceTree.subRequests) {
                  parentDataSourceTree.subRequests = [];
              }
              parentDataSourceTree.subRequests.push(subDataSourceTree);
          });
      }
  
      // handle m:n relations with join-table (insert an additional request into
      // the tree and move the original request one level deeper):
      if (resourceTree.joinVia) {
          const joinDataSource = dataSources[resourceTree.joinVia];
          joinDataSource.filter = [
              [
                  {
                      attribute:
                          joinDataSource.resolvedJoinParentKey.length === 1
                              ? joinDataSource.resolvedJoinParentKey[0]
                              : joinDataSource.resolvedJoinParentKey,
                      operator: 'equal',
                      valueFromParentKey: true
                  }
              ]
          ];
  
          const originalDataSourceTree = dataSourceTree;
          dataSourceTree = {
              attributePath: resourceTree.attrPath,
              dataSourceName: resourceTree.joinVia,
              parentKey: originalDataSourceTree.parentKey,
              childKey: joinDataSource.resolvedJoinParentKey,
              multiValuedParentKey: false,
              uniqueChildKey: originalDataSourceTree.uniqueChildKey,
              request: joinDataSource,
              attributeOptions: joinDataSource.attributeOptions,
              subRequests: [originalDataSourceTree]
          };
          originalDataSourceTree.parentKey = joinDataSource.resolvedJoinChildKey;
          originalDataSourceTree.uniqueChildKey = true;
  
          delete joinDataSource.attributeOptions;
          delete joinDataSource.joinParentKey;
          delete joinDataSource.resolvedJoinParentKey;
          delete joinDataSource.joinChildKey;
          delete joinDataSource.resolvedJoinChildKey;
      }
  
      return dataSourceTree;
  }