jec-sandcat
Version:
JEC Sandcat - The default RESTful web services framework for GlassCat applications.
348 lines (313 loc) • 12 kB
text/typescript
// DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
//
// Copyright 2016-2018 Pascal ECHEMANN.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {HttpRequest, HttpResponse, HttpJslet, JsletContext} from "jec-exchange";
import {HttpStatusCode, HttpMethod, HttpHeader} from "jec-commons";
import {ResourceJsletProxy} from "./ResourceJsletProxy";
import {ResourceDescriptor} from "../reflect/ResourceDescriptor";
import {MethodDescriptor} from "../reflect/MethodDescriptor";
import {ResponseHandlerBuilder} from "../builders/ResponseHandlerBuilder";
import {UrlPatternMapperBuilder} from "../builders/UrlPatternMapperBuilder";
import {UrlPatternMapper} from "../core/UrlPatternMapper";
import {UrlPatternMatcher} from "../core/UrlPatternMatcher";
import {ParameterInjector} from "../reflect/ParameterInjector";
import {SandcatError} from "../exceptions/SandcatError";
import {JsletMethod} from "../reflect/JsletMethod";
import {JsletMethodDescriptor} from "../reflect/JsletMethodDescriptor";
import {Sandcat} from "../Sandcat";
import {RequestPropertiesBuilder} from "../builders/RequestPropertiesBuilder";
import {RequestProperties} from "../utils/RequestProperties";
import {HttpHeadersValidator} from "../utils/HttpHeadersValidator";
import {SandcatLocaleManager} from "../i18n/SandcatLocaleManager";
import {SandcatLoggerProxy} from "../logging/SandcatLoggerProxy";
/**
* The <code>SandcatResourceJsletProxy</code> class is te default implementation
* for the <code>ResourceJsletProxy</code> interface in the Sandcat framework.
*/
export class SandcatResourceJsletProxy extends HttpJslet
implements ResourceJsletProxy {
//////////////////////////////////////////////////////////////////////////////
// Constructor function
//////////////////////////////////////////////////////////////////////////////
/**
* Creates a new <code>SandcatResourceJsletProxy</code> instance.
*/
constructor() {
super();
this.initObj();
}
//////////////////////////////////////////////////////////////////////////////
// Private properties
//////////////////////////////////////////////////////////////////////////////
/**
* The resource associated with this <code>ResourceJsletProxy</code>.
*/
private _resource:any = null;
/**
* The list of URL patterns associated with this <code>Jslet</code>.
*/
private _urlPatterns:string[] = null;
/**
* The name of this <code>Jslet</code>.
*/
private _name:string = null;
/**
* The helper object that creates callback handler functions.
*/
private _handlerBuilder:ResponseHandlerBuilder = null;
/**
* The helper object that performs mapping between URL patterns and the
* resource object.
*/
private _urlPatternMapper:UrlPatternMapper = null;
/**
* The helper object that injects parameters into a resource object method.
*/
private _paramInjector:ParameterInjector = null;
/**
* The helper object that validates HTTP header parameters for the current
* transaction.
*/
private _httpHeadersValidator:HttpHeadersValidator = null;
//////////////////////////////////////////////////////////////////////////////
// Private methods
//////////////////////////////////////////////////////////////////////////////
/**
* Initializes this object.
*/
private initObj():void {
this._handlerBuilder = new ResponseHandlerBuilder();
this._paramInjector = new ParameterInjector();
this._httpHeadersValidator = new HttpHeadersValidator();
}
/**
* Compute operations for sending correct parameters to the right resource
* method, depending on the HTTP metho and the UIR sub-route.
*
* @param {JsletMethod} jsletMethod the current HTTP transaction.
*/
private processJsletOperation(jsletMethod:JsletMethod):void {
const operation:JsletMethodDescriptor =
this._resource.getResourceDescriptor().jsletMethodsMap.get(jsletMethod);
let action:Function = null;
if(operation) {
action = operation.action;
action.apply(this._resource);
}
}
/**
* Exits the current HTTP request, whith a
* <code>HttpStatusCode.NOT_FOUND</code> status, when no mapped method is
* defined.
*
* @param {HttpRequest} req the HTTP request for the current HTTP transaction.
* @param {HttpResponse} res the HTTP response for the current HTTP
* transaction.
* @param {Function} exit the function used by the container to handle
* asynchronous answers for this jslet.
*/
private doNotFound(req:HttpRequest, res:HttpResponse,
exit:(req:HttpRequest, res:HttpResponse, data:any)=>void):void {
exit(req, res.sendStatus(HttpStatusCode.NOT_FOUND), null);
}
/**
* Compute operations for sending correct parameters to the right resource
* method, depending on the HTTP metho and the UIR sub-route.
*
* @param {HttpMethod} httpMethod the current HTTP transaction.TP transaction.
* @param {HttpResponse} res the HTTP response for the current HTTP
* transaction.
* @param {Function} exit the function used by the container to handle
* asynchronous answers for this jslet.
*/
private processOperation(httpMethod:HttpMethod,
req:HttpRequest,
res:HttpResponse,
exit:(req:HttpRequest, res:HttpResponse, data:any) => void):void {
const descriptor:ResourceDescriptor =
this._resource.getResourceDescriptor();
const requestProperties:RequestProperties =
RequestPropertiesBuilder.getInstance().build(httpMethod, req);
const patternMatcher:UrlPatternMatcher =
this._urlPatternMapper.matchRequest(requestProperties);
let action:Function = null;
let responseHandler:Function = null;
let operation:MethodDescriptor = null;
let parameters:any[] = null;
let operationStatus:number = -1;
let header:string = null;
if(patternMatcher) {
operation = descriptor.methodsMap
.get(patternMatcher.descriptor.getMappedMethod());
}
if(operation) {
header = descriptor.produces;
if(header) {
res.setHeader(HttpHeader.CONTENT_TYPE, header);
}
header = descriptor.crossDomainPolicy;
if(header) {
res.setHeader(HttpHeader.ACCESS_CONTROL_ALLOW_ORIGIN, header);
}
//TODO: add descriptor.consumes support
//TODO: implement a singleton helper to factorize such actions :
// header = descriptor.produces;
// res.setHeader(HttpHeader.CONTENT_TYPE, header);
operationStatus =
this._httpHeadersValidator.validate(operation, requestProperties);
if(operationStatus === HttpStatusCode.OK) {
responseHandler = this._handlerBuilder.build(req, res, exit);
parameters = this._paramInjector.buildParameters(
patternMatcher, responseHandler, operation, req
);
action = operation.action;
header = operation.produces;
if(header) {
res.setHeader(HttpHeader.CONTENT_TYPE, header);
}
header = operation.crossDomainPolicy;
if(header) {
res.setHeader(HttpHeader.ACCESS_CONTROL_ALLOW_ORIGIN, header);
}
action.apply(this._resource, parameters);
} else {
exit(req, res.sendStatus(operationStatus), null);
}
} else {
this.doNotFound(req, res, exit);
}
}
//////////////////////////////////////////////////////////////////////////////
// Public methods
//////////////////////////////////////////////////////////////////////////////
/**
* @inheritDoc
*/
public getResource():any {
return this._resource;
}
/**
* @inheritDoc
*/
public setResource(resource:any):void {
const mapperBuilder:UrlPatternMapperBuilder =
UrlPatternMapperBuilder.getInstance();
const descriptor:ResourceDescriptor = resource.getResourceDescriptor();
const resourceName:string = resource.constructor.name;
let message:string = null;
if(!descriptor){
message = SandcatLocaleManager.getInstance()
.get("errors.descriptor", resourceName);
SandcatLoggerProxy.getInstance().log(message);
throw new SandcatError(message);
}
this._resource = resource;
this._name = resourceName;
this._urlPatterns = descriptor.urlPatterns;
this._urlPatternMapper = mapperBuilder.build(descriptor);
}
/**
* @override
*/
public getUrlPatterns():string[] {
return this._urlPatterns;
}
/**
* @override
*/
public getName():string {
return this._name;
}
/**
* @inheritDoc
*/
public init():void {
this.processJsletOperation(JsletMethod.INIT);
}
/**
* @inheritDoc
*/
public destroy():void {
this.processJsletOperation(JsletMethod.DESTROY);
}
/**
* @inheritDoc
*/
public before():void {
this.processJsletOperation(JsletMethod.BEFORE);
}
/**
* @inheritDoc
*/
public after():void {
this.processJsletOperation(JsletMethod.AFTER);
}
/**
* @inheritDoc
*/
public doDelete(req:HttpRequest, res:HttpResponse,
exit:(req:HttpRequest, res:HttpResponse, data:any) => void):void {
this.processOperation(HttpMethod.DELETE, req, res, exit);
}
/**
* @inheritDoc
*/
public doGet(req:HttpRequest, res:HttpResponse,
exit:(req:HttpRequest, res:HttpResponse, data:any) => void):void {
this.processOperation(HttpMethod.GET, req, res, exit);
}
/**
* @inheritDoc
*/
public doOptions(req:HttpRequest, res:HttpResponse,
exit:(req:HttpRequest, res:HttpResponse, data:any) => void):void {
this.processOperation(HttpMethod.OPTIONS, req, res, exit);
}
/**
* @inheritDoc
*/
public doTrace(req:HttpRequest, res:HttpResponse,
exit:(req:HttpRequest, res:HttpResponse, data:any) => void):void {
this.processOperation(HttpMethod.TRACE, req, res, exit);
}
/**
* @inheritDoc
*/
public doHead(req:HttpRequest, res:HttpResponse,
exit:(req:HttpRequest, res:HttpResponse, data:any) => void):void {
this.processOperation(HttpMethod.HEAD, req, res, exit);
}
/**
* @inheritDoc
*/
public doConnect(req:HttpRequest, res:HttpResponse,
exit:(req:HttpRequest, res:HttpResponse, data:any) => void):void {
this.processOperation(HttpMethod.CONNECT, req, res, exit);
}
/**
* @inheritDoc
*/
public doPut(req:HttpRequest, res:HttpResponse,
exit:(req:HttpRequest, res:HttpResponse, data:any) => void):void {
this.processOperation(HttpMethod.PUT, req, res, exit);
}
/**
* @inheritDoc
*/
public doPost(req:HttpRequest, res:HttpResponse,
exit:(req:HttpRequest, res:HttpResponse, data:any) => void):void {
this.processOperation(HttpMethod.POST, req, res, exit);
}
}