Skip to content

Commit

Permalink
[FEATURE] MiddlewareUtil: Add getProject/getDependencies/resourceFact…
Browse files Browse the repository at this point in the history
…ory API to interface (#547)

Also pass "log" instance and "middlewareName" to custom middleware.

New API and parameters are only available to custom middleware defining specVerson 3.0 and later.

Based on RFC 0012 UI5 Tooling Extension API v3
  • Loading branch information
RandomByte authored Nov 29, 2022
1 parent a0e8eed commit ab28f78
Show file tree
Hide file tree
Showing 9 changed files with 538 additions and 88 deletions.
36 changes: 21 additions & 15 deletions lib/middleware/MiddlewareManager.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import middlewareRepository from "./middlewareRepository.js";
import MiddlewareUtil from "./MiddlewareUtil.js";
import logger from "@ui5/logger";
const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);

/**
Expand All @@ -20,20 +21,22 @@ const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
* @alias @ui5/server/internal/MiddlewareManager
*/
class MiddlewareManager {
constructor({graph, resources, options = {
constructor({graph, rootProject, resources, options = {
sendSAPTargetCSP: false,
serveCSPReports: false
}}) {
if (!graph || !resources || !resources.all || !resources.rootProject || !resources.dependencies) {
if (!graph || !rootProject || !resources || !resources.all ||
!resources.rootProject || !resources.dependencies) {
throw new Error("[MiddlewareManager]: One or more mandatory parameters not provided");
}
this.graph = graph;
this.rootProject = rootProject;
this.resources = resources;
this.options = options;

this.middleware = Object.create(null);
this.middlewareExecutionOrder = [];
this.middlewareUtil = new MiddlewareUtil();
this.middlewareUtil = new MiddlewareUtil({graph, project: rootProject});
}

/**
Expand Down Expand Up @@ -275,19 +278,22 @@ class MiddlewareManager {
await this.addMiddleware(middlewareDef.name, {
customMiddleware: async ({resources, middlewareUtil}) => {
const customMiddleware = this.graph.getExtension(middlewareDef.name);
const specVersion = customMiddleware.getSpecVersion();
const options = {
configuration: middlewareDef.configuration

const params = {
resources,
options: {
configuration: middlewareDef.configuration
}
};
const params = {resources, options};
if (
specVersion === "2.0" || specVersion === "2.1" ||
specVersion === "2.2" || specVersion === "2.3" ||
specVersion === "2.4" || specVersion === "2.5" ||
specVersion === "2.6"
) {
// Supply interface to MiddlewareUtil instance starting with specVersion 2.0
params.middlewareUtil = middlewareUtil.getInterface(specVersion);

const specVersion = customMiddleware.getSpecVersion();
if (specVersion.gte("3.0")) {
params.options.middlewareName = middlewareDef.name;
params.log = logger.getGroupLogger(`server:custom-middleware:${middlewareDef.name}`);
}
const middlewareUtilInterface = middlewareUtil.getInterface(specVersion);
if (middlewareUtilInterface) {
params.middlewareUtil = middlewareUtilInterface;
}
return (await customMiddleware.getMiddleware())(params);
},
Expand Down
185 changes: 161 additions & 24 deletions lib/middleware/MiddlewareUtil.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import parseurl from "parseurl";
import mime from "mime-types";
import {
createReaderCollectionPrioritized,
createResource,
createFilterReader,
createLinkReader,
createFlatReader
} from "@ui5/fs/resourceFactory";

/**
* Convenience functions for UI5 Server middleware.
Expand All @@ -17,33 +24,21 @@ import mime from "mime-types";
*/
class MiddlewareUtil {
/**
* Get an interface to an instance of this class that only provides those functions
* that are supported by the given custom middleware extension specification version.
*
* @param {string} specVersion Specification Version of custom middleware extension
* @returns {object} An object with bound instance methods supported by the given specification version
* @param {object} parameters
* @param {@ui5/project/graph/ProjectGraph} parameters.graph Relevant ProjectGraph
* @param {@ui5/project/specifications/Project} parameters.project Project that is being served
* @public
*/
getInterface(specVersion) {
const baseInterface = {
getPathname: this.getPathname.bind(this),
getMimeInfo: this.getMimeInfo.bind(this)
};
switch (specVersion) {
case "0.1":
case "1.0":
case "1.1":
return undefined;
case "2.0":
case "2.1":
case "2.2":
case "2.3":
case "2.4":
case "2.5":
case "2.6":
return baseInterface;
default:
throw new Error(`MiddlewareUtil: Unknown or unsupported Specification Version ${specVersion}`);
constructor({graph, project}) {
if (!graph) {
throw new Error(`Missing parameter "graph"`);
}
if (!project) {
throw new Error(`Missing parameter "project"`);
}
this._graph = graph;
this._project = project;
}

/**
Expand Down Expand Up @@ -100,6 +95,148 @@ class MiddlewareUtil {
contentType: type + (charset ? "; charset=" + charset : "")
};
}
/**
* Specification Version-dependent [Project]{@link @ui5/project/specifications/Project} interface.
* For details on individual functions, see [Project]{@link @ui5/project/specifications/Project}
*
* @public
* @typedef {object} @ui5/project/build/helpers/MiddlewareUtill~ProjectInterface
* @property {Function} getType Get the project type
* @property {Function} getName Get the project name
* @property {Function} getVersion Get the project version
* @property {Function} getNamespace Get the project namespace
* @property {Function} getRootReader Get the project rootReader
* @property {Function} getReader Get the project reader
* @property {Function} getCustomConfiguration Get the project Custom Configuration
* @property {Function} isFrameworkProject Check whether the project is a UI5-Framework project
*/

/**
* Retrieve a single project from the dependency graph
*
* </br></br>
* This method is only available to custom server middleware extensions defining
* <b>Specification Version 3.0 and above</b>.
*
* @param {string} [projectName] Name of the project to retrieve. Defaults to the project currently being built
* @returns {@ui5/project/build/helpers/MiddlewareUtill~ProjectInterface|undefined}
* project instance or undefined if the project is unknown to the graph
* @public
*/
getProject(projectName) {
if (projectName) {
return this._graph.getProject(projectName);
}
return this._project;
}

/**
* Retrieve a list of direct dependencies of a given project from the dependency graph.
* Note that this list does not include transitive dependencies.
*
* </br></br>
* This method is only available to custom server middleware extensions defining
* <b>Specification Version 3.0 and above</b>.
*
* @param {string} [projectName] Name of the project to retrieve. Defaults to the project currently being built
* @returns {string[]} Names of all direct dependencies
* @throws {Error} If the requested project is unknown to the graph
* @public
*/
getDependencies(projectName) {
return this._graph.getDependencies(projectName || this._project.getName());
}

/**
* Specification Version-dependent set of [@ui5/fs/resourceFactory]{@link @ui5/fs/resourceFactory}
* functions provided to middleware.
* For details on individual functions, see [@ui5/fs/resourceFactory]{@link @ui5/fs/resourceFactory}
*
* @public
* @typedef {object} @ui5/project/build/helpers/MiddlewareUtill~resourceFactory
* @property {Function} createResource Creates a [Resource]{@link @ui5/fs/Resource}.
* Accepts the same parameters as the [Resource]{@link @ui5/fs/Resource} constructor.
* @property {Function} createReaderCollectionPrioritized Creates a prioritized reader collection:
* [ReaderCollectionPrioritized]{@link @ui5/fs/ReaderCollectionPrioritized}
* @property {Function} createFilterReader
* Create a [Filter-Reader]{@link @ui5/fs/readers/Filter} with the given reader.
* @property {Function} createLinkReader
* Create a [Link-Reader]{@link @ui5/fs/readers/Filter} with the given reader.
* @property {Function} createFlatReader Create a [Link-Reader]{@link @ui5/fs/readers/Link}
* where all requests are prefixed with <code>/resources/<namespace></code>.
*/

/**
* Provides limited access to [@ui5/fs/resourceFactory]{@link @ui5/fs/resourceFactory} functions
*
* </br></br>
* This attribute is only available to custom server middleware extensions defining
* <b>Specification Version 3.0 and above</b>.
*
* @type {@ui5/project/build/helpers/MiddlewareUtill~resourceFactory}
* @public
*/
resourceFactory = {
createResource,
createReaderCollectionPrioritized,
createFilterReader,
createLinkReader,
createFlatReader,
};

/**
* Get an interface to an instance of this class that only provides those functions
* that are supported by the given custom middleware extension specification version.
*
* @param {@ui5/project/specifications/SpecificationVersion} specVersion
* SpecVersionComparator instance of the custom server middleware
* @returns {object} An object with bound instance methods supported by the given specification version
*/
getInterface(specVersion) {
if (specVersion.lt("2.0")) {
// Custom middleware defining specVersion <2.0 does not have access to any MiddlewareUtil API
return undefined;
}

const baseInterface = {};
bindFunctions(this, baseInterface, [
"getPathname", "getMimeInfo"
]);

if (specVersion.gte("3.0")) {
// getProject function, returning an interfaced project instance
baseInterface.getProject = (projectName) => {
const project = this.getProject(projectName);
const baseProjectInterface = {};
bindFunctions(project, baseProjectInterface, [
"getType", "getName", "getVersion", "getNamespace",
"getRootReader", "getReader", "getCustomConfiguration", "isFrameworkProject"
]);
return baseProjectInterface;
};
// getDependencies function, returning an array of project names
baseInterface.getDependencies = (projectName) => {
return this.getDependencies(projectName);
};

baseInterface.resourceFactory = Object.create(null);
[
// Once new functions get added, extract this array into a variable
// and enhance based on spec version once new functions get added
"createResource", "createReaderCollectionPrioritized",
"createFilterReader", "createLinkReader", "createFlatReader",
].forEach((factoryFunction) => {
baseInterface.resourceFactory[factoryFunction] = this.resourceFactory[factoryFunction];
});
}
return baseInterface;
}
}

function bindFunctions(sourceObject, targetObject, funcNames) {
funcNames.forEach((funcName) => {
targetObject[funcName] = sourceObject[funcName].bind(sourceObject);
});
}

export default MiddlewareUtil;
2 changes: 1 addition & 1 deletion lib/middleware/serveResources.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function createMiddleware({resources, middlewareUtil}) {
let propertiesFileSourceEncoding = project?.getPropertiesFileSourceEncoding();

if (!propertiesFileSourceEncoding) {
if (project && ["0.1", "1.0", "1.1"].includes(project.getSpecVersion())) {
if (project && project.getSpecVersion().lte("1.1")) {
// default encoding to "ISO-8859-1" for old specVersions
propertiesFileSourceEncoding = "ISO-8859-1";
} else {
Expand Down
1 change: 1 addition & 0 deletions lib/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export async function serve(graph, {

const middlewareManager = new MiddlewareManager({
graph,
rootProject,
resources,
options: {
sendSAPTargetCSP,
Expand Down
Loading

0 comments on commit ab28f78

Please sign in to comment.