AppContext ing

This commit is contained in:
crusader 2017-07-27 20:28:51 +09:00
parent 09226de460
commit 01bd5e5f94
17 changed files with 622 additions and 12 deletions

View File

@ -42,7 +42,7 @@ import {
import * as injectTapEventPlugin from 'react-tap-event-plugin'; import * as injectTapEventPlugin from 'react-tap-event-plugin';
import Platform from '@overflow/commons/platform'; import Platform from '@overflow/commons/platform';
import AppContext from '@overflow/commons/context'; import AppContext from '@overflow/commons/context/AppContext';
import * as AppContextLifecycleActions from '@overflow/commons/context/redux/action/lifecycle'; import * as AppContextLifecycleActions from '@overflow/commons/context/redux/action/lifecycle';
import WebSocketRPC from '@overflow/commons/websocket/WebSocketRPC'; import WebSocketRPC from '@overflow/commons/websocket/WebSocketRPC';
import ReducerContext from '@overflow/commons/redux/ReducerContext'; import ReducerContext from '@overflow/commons/redux/ReducerContext';
@ -86,8 +86,8 @@ class Application {
this.displayLoading(); this.displayLoading();
this.context = await this.initContext(); this.context = await this.initContext();
// this.rpcClient = await this.initRpcClient(); this.rpcClient = await this.initRpcClient();
// this.context.put(this.rpcClient); this.context.PouchFactory.registerPouch(this.rpcClient);
await this.initRedux(); await this.initRedux();
@ -102,7 +102,7 @@ class Application {
private initContext(): Promise<AppContext> { private initContext(): Promise<AppContext> {
const appContext = new Promise<AppContext>(resolve => { const appContext = new Promise<AppContext>(resolve => {
const context = AppContext.getContext(); const context = AppContext.getInstance();
resolve(context); resolve(context);
}); });

View File

@ -1,8 +1,8 @@
import inject from '../context/decorator/inject'; import Inject from '../context/pouches/decorator/Inject';
import WebSocketRPC from '../websocket/WebSocketRPC'; import WebSocketRPC from '../websocket/WebSocketRPC';
abstract class Service { abstract class Service {
@inject({qualifier: '11111', required: false}) @Inject({required: false})
private webSocketRPC: WebSocketRPC; private webSocketRPC: WebSocketRPC;
private name: string; private name: string;
protected constructor(name: string) { protected constructor(name: string) {

View File

@ -0,0 +1,41 @@
import PouchFactory from './pouches/factory/PouchFactory';
import {
ClassConstructor,
} from './pouches/constants';
class AppContext {
private static _instance: AppContext;
private _pouchFactory: PouchFactory;
public constructor() {
this._pouchFactory = new PouchFactory();
}
public get PouchFactory(): PouchFactory {
return this._pouchFactory;
}
/**
* getPouch
*/
public getPouch<T>(type: ClassConstructor<T>, ...args: any[]): T {
return this._pouchFactory.getPouch(type, args);
}
/**
* getPouchByQualifier
*/
public getPouchByQualifier<T>(type: ClassConstructor<T>, qualifier: string | symbol, ...args: any[]): T {
return this._pouchFactory.getPouchByQualifier(type, qualifier, args);
}
public static getInstance(): AppContext {
if (undefined === AppContext._instance) {
AppContext._instance = new AppContext();
}
return AppContext._instance;
}
}
export default AppContext;

View File

@ -21,7 +21,7 @@ class AppContext {
this.instanceMap.set(aaa, instance); this.instanceMap.set(aaa, instance);
} }
private setValue(type: any, target: object): void { private setValue(type: Function, target: object): void {
if (type.constructor === Object) { if (type.constructor === Object) {
console.log('Object'); console.log('Object');
return; return;
@ -44,7 +44,7 @@ class AppContext {
} }
public static getService<T>(type: ClassConstructor<T>, ...args: any[]): T { public static getService<T>(type: ClassConstructor<T>, ...args: any[]): T {
let clazz = type.prototype; let clazz: Function = type.prototype;
let instance = Object.create(clazz); let instance = Object.create(clazz);
instance.constructor.apply(instance, args); instance.constructor.apply(instance, args);

View File

@ -0,0 +1,59 @@
import { PouchScope, POUCH_DEFAULT_QUALIFIER } from '../constants';
class PouchDefinition {
protected _type: Function;
protected _scope: PouchScope = PouchScope.SINGLETON;
protected _qualifier: string | symbol;
protected _postConstruct: string[] = null;
protected _preDestroy: string[] = null;
public constructor(type: Function) {
this._type = type;
this._qualifier = POUCH_DEFAULT_QUALIFIER;
}
public get type(): Function {
return this._type;
}
public get scope(): PouchScope {
return this._scope;
}
public set scope(scope: PouchScope) {
this._scope = scope;
}
public get qualifier(): string | symbol {
return this._qualifier;
}
public set qualifier(qualifier: string | symbol) {
this._qualifier = qualifier;
}
public get postConstruct(): string[] {
return this._postConstruct;
}
public addPostConstruct(postConstruct: string): void {
if (null == this._postConstruct) {
this._postConstruct = [];
}
this._postConstruct.push(postConstruct);
}
public get preDestroy(): string[] {
return this._preDestroy;
}
public addPreDestroy(preDestroy: string): void {
if (null == this._preDestroy) {
this._preDestroy = [];
}
this._preDestroy.push(preDestroy);
}
}
export default PouchDefinition;

View File

@ -0,0 +1,92 @@
import * as METADATA from '../constants';
import { DecoratorType, POUCH_DEFAULT_QUALIFIER } from '../constants';
import InjectConfig from '../decorator/InjectConfig';
export interface InjectItem {
decoratorType: DecoratorType;
propertyConfig?: InjectConfig;
parameterConfigMap?: Map<number, InjectConfig>;
}
class PouchInjectDefinition {
protected target: Object;
protected injectMap: Map<string | symbol, InjectItem>;
public constructor(target: Object) {
this.target = target;
this.injectMap = new Map();
}
public get injectors(): Map<string | symbol, InjectItem> {
return this.injectMap;
}
/**
* addInject
*/
public addInject(injectConfig: InjectConfig, propertyKey: string | symbol, parameterIndex?: number): void {
if (undefined === injectConfig.qualifier) {
injectConfig.qualifier = POUCH_DEFAULT_QUALIFIER;
}
if (undefined === injectConfig.required) {
injectConfig.required = false;
}
if(typeof parameterIndex === 'number') {
this.addInjectParameter(injectConfig, propertyKey, parameterIndex);
} else {
this.addInjectProperty(injectConfig, propertyKey);
}
}
private addInjectParameter(injectConfig: InjectConfig, propertyKey: string | symbol, parameterIndex: number): void {
let injectItem: InjectItem;
let parameterTypes: any[] = Reflect.getMetadata(METADATA.DESIGN_PARAMTYPES, this.target, propertyKey);
if (undefined === injectConfig.type) {
injectConfig.type = parameterTypes[parameterIndex];
}
if (this.injectMap.has(propertyKey)) {
injectItem = this.injectMap.get(propertyKey);
} else {
injectItem = {
decoratorType: DecoratorType.PARAMETER,
};
this.injectMap.set(propertyKey, injectItem);
}
if (null !== injectItem.parameterConfigMap) {
if (injectItem.parameterConfigMap.has(parameterIndex)) {
throw new Error(`Cannot apply @inject decorator on one parameter[${propertyKey}:${parameterIndex}] multiple times.`);
}
} else {
injectItem.parameterConfigMap = new Map();
}
injectItem.parameterConfigMap.set(parameterIndex, injectConfig);
}
private addInjectProperty(injectConfig: InjectConfig, propertyKey: string | symbol): void {
if (this.injectMap.has(propertyKey)) {
throw new Error(`Cannot apply @inject decorator on one property[${propertyKey}] multiple times.`);
}
let propertyType: any = Reflect.getMetadata(METADATA.DESIGN_TYPE, this.target, propertyKey);
if (undefined === injectConfig.type) {
injectConfig.type = propertyType;
}
let injectItem: InjectItem = {
decoratorType: DecoratorType.PROPERTY,
propertyConfig: injectConfig,
};
this.injectMap.set(propertyKey, injectItem);
}
}
export default PouchInjectDefinition;

View File

@ -0,0 +1,29 @@
// used to access design time types
export const DESIGN_TYPE = 'design:type';
// used to access design time types
export const DESIGN_PARAMTYPES = 'design:paramtypes';
// used to access design time types
export const DESIGN_RETURNTYPE = 'design:returntype';
// used to store types to be injected
export const POUCH_DEFINITION = 'loafer:pouch_definition';
// used to store types to be injected
export const POUCH_INJECT_DEFINITION = 'loafer:pouch_inject_definition';
export const POUCH_DEFAULT_QUALIFIER = Symbol('__QUALIFIER__');
export enum PouchScope {
SINGLETON,
PROTOTYPE,
}
export enum DecoratorType {
CLASS,
PROPERTY,
PARAMETER,
METHOD,
}
export type ClassConstructor<T> = {new(...args: any[]): T};

View File

@ -0,0 +1,66 @@
import * as METADATA from '../constants';
import { POUCH_DEFAULT_QUALIFIER } from '../constants';
import InjectConfig from './InjectConfig';
import PouchInjectDefinition from '../config/PouchInjectDefinition';
const Inject = (injectConfig: InjectConfig = {}) => {
return (...args: any[]) => {
switch(args.length) {
case 1:
return injectClass.apply(this, args);
case 2:
return injectProperty.apply(this, args);
case 3:
if(typeof args[2] === 'number') {
return injectParameter.apply(this, args);
}
return injectMethod.apply(this, args);
default:
throw new Error('@Inject decorators are not valid here!');
}
};
};
const injectClass = (injectConfig: InjectConfig = {}) => {
return <TFunction extends Function>(target: TFunction): TFunction | void => {
throw new Error('Cannot apply @Inject decorator on Class.');
};
};
const injectProperty = (injectConfig: InjectConfig = {}) => {
return (target: Object, propertyKey: string | symbol): void => {
let pouchInjectDefinition: PouchInjectDefinition = getPouchInjectDefinition(target);
pouchInjectDefinition.addInject(injectConfig, propertyKey);
};
};
const injectParameter = (injectConfig: InjectConfig = {}) => {
return (target: Object, propertyKey: string | symbol, parameterIndex: number): void => {
let pouchInjectDefinition: PouchInjectDefinition = getPouchInjectDefinition(target);
pouchInjectDefinition.addInject(injectConfig, propertyKey, parameterIndex);
};
};
const injectMethod = (injectConfig: InjectConfig = {}) => {
return (target: Object, propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<any>): TypedPropertyDescriptor<any> | void => {
throw new Error('Cannot apply @Inject decorator on Class.');
// return descriptor;
};
};
const getPouchInjectDefinition = (target: Object) => {
let pouchInjectDefinition: PouchInjectDefinition;
if (Reflect.hasOwnMetadata(METADATA.POUCH_INJECT_DEFINITION, target) !== true) {
pouchInjectDefinition = new PouchInjectDefinition(target);
Reflect.defineMetadata(METADATA.POUCH_INJECT_DEFINITION, pouchInjectDefinition, target);
} else {
pouchInjectDefinition = Reflect.getMetadata(METADATA.POUCH_INJECT_DEFINITION, target);
}
return pouchInjectDefinition;
};
export default Inject;

View File

@ -0,0 +1,7 @@
interface InjectConfig {
qualifier?: string | symbol;
required?: boolean;
type?: any;
}
export default InjectConfig;

View File

@ -0,0 +1,60 @@
import * as METADATA from '../constants';
import PouchDefinition from '../config/PouchDefinition';
const Injectable = (...args: any[]) => {
switch(args.length) {
case 1:
return injectableClass.apply(this, args);
case 2:
return injectableProperty.apply(this, args);
case 3:
if(typeof args[2] === 'number') {
return injectableParameter.apply(this, args);
}
return injectableMethod.apply(this, args);
default:
throw new Error('@Injectable decorators are not valid here!');
}
};
const injectableClass = <TFunction extends Function>(target: TFunction): TFunction | void => {
if (Reflect.hasOwnMetadata(METADATA.POUCH_DEFINITION, target) === true) {
throw new Error('Cannot apply @injectable decorator multiple times.');
}
let pouchDefinition: PouchDefinition = getPouchDefinition(target.prototype);
return target;
};
const injectableProperty = (target: Object, propertyKey: string | symbol): void => {
throw new Error('Cannot apply @Injectable decorator on property.');
};
const injectableParameter = (target: Object, propertyKey: string | symbol, parameterIndex: number): void => {
throw new Error('Cannot apply @Injectable decorator on parameter.');
};
const injectableMethod = <T>(target: Object, propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<T>): TypedPropertyDescriptor<T> | void => {
throw new Error('Cannot apply @Injectable decorator on method.');
// return descriptor;
};
const getPouchDefinition = <TFunction extends Function>(target: TFunction) => {
let pouchDefinition: PouchDefinition;
if (Reflect.hasOwnMetadata(METADATA.POUCH_DEFINITION, target) !== true) {
pouchDefinition = new PouchDefinition(target);
Reflect.defineMetadata(METADATA.POUCH_DEFINITION, pouchDefinition, target);
} else {
pouchDefinition = Reflect.getMetadata(METADATA.POUCH_DEFINITION, target);
}
return pouchDefinition;
};
export default Injectable;

View File

@ -0,0 +1,14 @@
import * as METADATA from '../constants';
import PouchDefinition from '../config/PouchDefinition';
const PostConstruct = (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>): TypedPropertyDescriptor<any> => {
if (Reflect.hasOwnMetadata(METADATA.POUCH_DEFINITION, target) !== true) {
throw new Error('Cannot apply @PostConstruct decorator on the not @Injectable class.');
}
let pouchDefinition: PouchDefinition = Reflect.getMetadata(METADATA.POUCH_DEFINITION, target);
pouchDefinition.addPostConstruct(propertyKey);
return descriptor;
};
export default PostConstruct;

View File

@ -0,0 +1,14 @@
import * as METADATA from '../constants';
import PouchDefinition from '../config/PouchDefinition';
const PreDestroy = (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>): TypedPropertyDescriptor<any> => {
if (Reflect.hasOwnMetadata(METADATA.POUCH_DEFINITION, target) !== true) {
throw new Error('Cannot apply @PreDestroy decorator on the not @Injectable class.');
}
let pouchDefinition: PouchDefinition = Reflect.getMetadata(METADATA.POUCH_DEFINITION, target);
pouchDefinition.addPreDestroy(propertyKey);
return descriptor;
};
export default PreDestroy;

View File

@ -0,0 +1,16 @@
import * as METADATA from '../constants';
import PouchDefinition from '../config/PouchDefinition';
const Qualifier = (value: string | symbol) => {
return <TFunction extends Function>(target: TFunction): TFunction | void => {
if (Reflect.hasOwnMetadata(METADATA.POUCH_DEFINITION, target) !== true) {
throw new Error('Cannot apply @Qualifier decorator on the not @Injectable class.');
}
let pouchDefinition: PouchDefinition = Reflect.getMetadata(METADATA.POUCH_DEFINITION, target);
pouchDefinition.qualifier = value;
return target;
};
};
export default Qualifier;

View File

@ -0,0 +1,17 @@
import * as METADATA from '../constants';
import PouchDefinition from '../config/PouchDefinition';
import { PouchScope } from '../constants';
const Scope = (value: PouchScope = PouchScope.SINGLETON) => {
return <TFunction extends Function>(target: TFunction): TFunction | void => {
if (Reflect.hasOwnMetadata(METADATA.POUCH_DEFINITION, target) !== true) {
throw new Error('Cannot apply @Scope decorator on the not @Injectable class.');
}
let pouchDefinition: PouchDefinition = Reflect.getMetadata(METADATA.POUCH_DEFINITION, target);
pouchDefinition.scope = value;
return target;
};
};
export default Scope;

View File

@ -0,0 +1,195 @@
import * as METADATA from '../constants';
import {
ClassConstructor,
DecoratorType,
PouchScope,
POUCH_DEFAULT_QUALIFIER,
} from '../constants';
import PouchDefinition from '../config/PouchDefinition';
import PouchInjectDefinition, {
InjectItem,
} from '../config/PouchInjectDefinition';
import InjectConfig from '../decorator/InjectConfig';
class PouchFactory {
private pouchDefinitionMap: Map<Function, PouchDefinition>;
private pouchInstanceMap: Map<Function, Map<string | symbol, any>>;
public constructor() {
this.pouchDefinitionMap = new Map();
this.pouchInstanceMap = new Map();
}
/**
* getPouch
*/
public getPouch<T>(type: ClassConstructor<T>, ...args: any[]): T {
let qualifier = POUCH_DEFAULT_QUALIFIER;
return this.getPouchByQualifier(type, qualifier, args);
}
/**
* getPouchByQualifier
*/
public getPouchByQualifier<T>(type: ClassConstructor<T>, qualifier: string | symbol, ...args: any[]): T {
let clazz: Function = type.prototype;
let instance = this._getPouch(clazz, qualifier, args);
return instance;
}
/**
* registerPouch
*/
public registerPouch(pouch: any, qualifier: string | symbol = POUCH_DEFAULT_QUALIFIER): void {
let clazz = pouch.prototype;
this._putPouchInstance(clazz, qualifier, pouch);
}
/**
* registerPouch
*/
public registerPouchDefinition<T>(type: ClassConstructor<T>, pouchDefinition: PouchDefinition): void {
let clazz: Function = type.prototype;
if (this.pouchDefinitionMap.has(clazz)) {
throw new Error(`Pouch Definition of [${clazz.name}] is already exist.`);
}
this.pouchDefinitionMap.set(clazz, pouchDefinition);
}
protected getPouchDefinition(clazz: Function): PouchDefinition {
let pouchDefinition: PouchDefinition = this.pouchDefinitionMap.get(clazz);
if (undefined !== pouchDefinition) {
return pouchDefinition;
}
if (Reflect.hasOwnMetadata(METADATA.POUCH_DEFINITION, clazz) !== true) {
return null;
}
pouchDefinition = Reflect.getMetadata(METADATA.POUCH_DEFINITION, clazz);
this.pouchDefinitionMap.set(clazz, pouchDefinition);
return pouchDefinition;
}
protected getPouchInjectDefinition(clazz: Function): PouchInjectDefinition {
if (Reflect.hasOwnMetadata(METADATA.POUCH_INJECT_DEFINITION, clazz) !== true) {
return null;
}
let pouchInjectDefinition: PouchInjectDefinition = Reflect.getMetadata(METADATA.POUCH_INJECT_DEFINITION, clazz);
return pouchInjectDefinition;
}
private _getPouch(clazz: Function, qualifier: string | symbol, ...args: any[]): any {
let pouchDefinition: PouchDefinition = this.getPouchDefinition(clazz);
let instance: any;
if (null == pouchDefinition) {
if (this._hasPouchInstance(clazz, qualifier)) {
instance = this._getPouchInstance(clazz, qualifier);
} else {
instance = this._newInstance(clazz, pouchDefinition, args);
}
} else {
if (pouchDefinition.scope === PouchScope.PROTOTYPE) {
instance = this._newInstance(clazz, pouchDefinition, args);
} else {
if (this._hasPouchInstance(clazz, qualifier)) {
return this._getPouchInstance(clazz, qualifier);
}
instance = this._newInstance(clazz, pouchDefinition, args);
}
}
return instance;
}
private _newInstance(clazz: Function, pouchDefinition: PouchDefinition, ...args: any[]): any {
let instance: any;
instance = Object.create(clazz);
instance.constructor.apply(instance, args);
this._injectDependency(instance, clazz);
return instance;
}
private _injectDependency(target: object, clazz: Function): void {
if (clazz.constructor === Object) {
return;
}
let pouchInjectDefinition: PouchInjectDefinition = this.getPouchInjectDefinition(clazz);
if (null !== pouchInjectDefinition) {
let injectors: Map<string | symbol, InjectItem> = pouchInjectDefinition.injectors;
let propertyDescriptor: any;
injectors.forEach((injectItem, key, map) => {
propertyDescriptor = Object.getOwnPropertyDescriptor(target, key);
switch (injectItem.decoratorType) {
case DecoratorType.PROPERTY:
this._injectDependencyProperty(target, clazz, key, propertyDescriptor, injectItem.propertyConfig);
break;
case DecoratorType.PARAMETER:
this._injectDependencyParameter(target, propertyDescriptor, injectItem.parameterConfigMap);
break;
default:
break;
}
});
}
this._injectDependency(target, Object.getPrototypeOf(clazz));
}
private _injectDependencyProperty(target: object, clazz: Function, propertyKey: string | symbol,
propertyDescriptor: PropertyDescriptor, injectConfig: InjectConfig): void {
let instance = this.getPouch(injectConfig.type, injectConfig.qualifier);
if (injectConfig.required && undefined === instance) {
throw new Error(`Pouch which used by [${clazz.name}.${propertyKey}] is not exist in the context.`);
}
target[propertyKey] = instance;
}
private _injectDependencyParameter(target: object, propertyDescriptor: PropertyDescriptor,
parameterConfigs: Map<number, InjectConfig>): void {
console.log('');
}
private _hasPouchInstance(clazz: Function, qualifier: string | symbol): boolean {
if (!this.pouchInstanceMap.has(clazz) || !this.pouchInstanceMap.get(clazz).has(qualifier)) {
return false;
}
return true;
}
private _getPouchInstance(clazz: Function, qualifier: string | symbol): any {
if (!this._hasPouchInstance(clazz, qualifier)) {
return null;
}
return this.pouchInstanceMap.get(clazz).get(qualifier);
}
private _putPouchInstance(clazz: Function, qualifier: string | symbol, instance: any): void {
let instanceMap: Map<string | symbol, any> = this.pouchInstanceMap.get(clazz);
if (undefined === instanceMap) {
instanceMap = new Map();
this.pouchInstanceMap.set(clazz, instanceMap);
}
instanceMap.set(qualifier, instance);
}
}
export default PouchFactory;

View File

@ -1,11 +1,11 @@
import Service from '@overflow/commons/api/Service'; import Service from '@overflow/commons/api/Service';
import Member from '../model/Member'; import Member from '../model/Member';
import injectable from '@overflow/commons/context/decorator/injectable'; import Injectable from '@overflow/commons/context/pouches/decorator/Injectable';
import inject from '@overflow/commons/context/decorator/inject'; import inject from '@overflow/commons/context/decorator/inject';
@injectable() @Injectable
export class MemberService extends Service { export class MemberService extends Service {
public constructor() { public constructor() {

View File

@ -3,7 +3,7 @@ import { call, Effect, fork, put, takeLatest } from 'redux-saga/effects';
import { SagaWatcher } from '@overflow/commons/redux-saga'; import { SagaWatcher } from '@overflow/commons/redux-saga';
import AppContext from '@overflow/commons/context'; import AppContext from '@overflow/commons/context/AppContext';
import Action from '@overflow/commons/redux/Action'; import Action from '@overflow/commons/redux/Action';
import Member from '../../api/model/Member'; import Member from '../../api/model/Member';
@ -18,7 +18,7 @@ function* signin(action: Action<SigninPayload>): SagaIterator {
// type: types.SENDING_REQUEST, // type: types.SENDING_REQUEST,
// payload: {sendingRequest: true}, // payload: {sendingRequest: true},
// }); // });
let memberService = AppContext.getService(MemberService); let memberService = AppContext.getInstance().getPouch(MemberService);
const member = yield call({context: memberService, fn: memberService.signin}, signinId, signinPw); const member = yield call({context: memberService, fn: memberService.signin}, signinId, signinPw);