mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-13 04:53:06 +00:00
More refactoring + reusing the server's store on the client
This commit is contained in:
@@ -10,13 +10,6 @@ import { AppComponent } from './app.component';
|
|||||||
import { HeaderComponent } from './header/header.component';
|
import { HeaderComponent } from './header/header.component';
|
||||||
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
||||||
|
|
||||||
import { StoreModule } from "@ngrx/store";
|
|
||||||
import { RouterStoreModule } from "@ngrx/router-store";
|
|
||||||
import { StoreDevtoolsModule } from "@ngrx/store-devtools";
|
|
||||||
|
|
||||||
import { rootReducer } from './app.reducers';
|
|
||||||
import { effects } from './app.effects';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
@@ -28,34 +21,6 @@ import { effects } from './app.effects';
|
|||||||
HomeModule,
|
HomeModule,
|
||||||
CoreModule.forRoot(),
|
CoreModule.forRoot(),
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
/**
|
|
||||||
* StoreModule.provideStore is imported once in the root module, accepting a reducer
|
|
||||||
* function or object map of reducer functions. If passed an object of
|
|
||||||
* reducers, combineReducers will be run creating your application
|
|
||||||
* meta-reducer. This returns all providers for an @ngrx/store
|
|
||||||
* based application.
|
|
||||||
*/
|
|
||||||
StoreModule.provideStore(rootReducer),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngrx/router-store keeps router state up-to-date in the store and uses
|
|
||||||
* the store as the single source of truth for the router's state.
|
|
||||||
*/
|
|
||||||
RouterStoreModule.connectRouter(),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store devtools instrument the store retaining past versions of state
|
|
||||||
* and recalculating new states. This enables powerful time-travel
|
|
||||||
* debugging.
|
|
||||||
*
|
|
||||||
* To use the debugger, install the Redux Devtools extension for either
|
|
||||||
* Chrome or Firefox
|
|
||||||
*
|
|
||||||
* See: https://github.com/zalmoxisus/redux-devtools-extension
|
|
||||||
*/
|
|
||||||
StoreDevtoolsModule.instrumentOnlyWithExtension(),
|
|
||||||
|
|
||||||
effects
|
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
]
|
]
|
||||||
|
@@ -3,6 +3,7 @@ import { routerReducer, RouterState } from "@ngrx/router-store";
|
|||||||
import { headerReducer, HeaderState } from './header/header.reducer';
|
import { headerReducer, HeaderState } from './header/header.reducer';
|
||||||
import { hostWindowReducer, HostWindowState } from "./shared/host-window.reducer";
|
import { hostWindowReducer, HostWindowState } from "./shared/host-window.reducer";
|
||||||
import { CoreState, coreReducer } from "./core/core.reducers";
|
import { CoreState, coreReducer } from "./core/core.reducers";
|
||||||
|
import { StoreActionTypes } from "./store.actions";
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
core: CoreState;
|
core: CoreState;
|
||||||
@@ -19,5 +20,10 @@ export const reducers = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function rootReducer(state: any, action: any) {
|
export function rootReducer(state: any, action: any) {
|
||||||
|
if (action.type === StoreActionTypes.REHYDRATE) {
|
||||||
|
state = action.payload;
|
||||||
|
}
|
||||||
return combineReducers(reducers)(state, action);
|
return combineReducers(reducers)(state, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const NGRX_CACHE_KEY = "NGRX_STORE";
|
||||||
|
4
src/app/core/cache/cache-entry.ts
vendored
Normal file
4
src/app/core/cache/cache-entry.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export interface CacheEntry {
|
||||||
|
timeAdded: number;
|
||||||
|
msToLive: number;
|
||||||
|
}
|
5
src/app/core/cache/object-cache.actions.ts
vendored
5
src/app/core/cache/object-cache.actions.ts
vendored
@@ -11,11 +11,12 @@ export class AddToObjectCacheAction implements Action {
|
|||||||
type = ObjectCacheActionTypes.ADD;
|
type = ObjectCacheActionTypes.ADD;
|
||||||
payload: {
|
payload: {
|
||||||
objectToCache: CacheableObject;
|
objectToCache: CacheableObject;
|
||||||
|
timeAdded: number;
|
||||||
msToLive: number;
|
msToLive: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(objectToCache: CacheableObject, msToLive: number) {
|
constructor(objectToCache: CacheableObject, timeAdded: number, msToLive: number) {
|
||||||
this.payload = { objectToCache, msToLive };
|
this.payload = { objectToCache, timeAdded, msToLive };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
5
src/app/core/cache/object-cache.reducer.ts
vendored
5
src/app/core/cache/object-cache.reducer.ts
vendored
@@ -1,11 +1,12 @@
|
|||||||
import { ObjectCacheAction, ObjectCacheActionTypes, AddToObjectCacheAction, RemoveFromObjectCacheAction } from "./object-cache.actions";
|
import { ObjectCacheAction, ObjectCacheActionTypes, AddToObjectCacheAction, RemoveFromObjectCacheAction } from "./object-cache.actions";
|
||||||
import { hasValue } from "../../shared/empty.util";
|
import { hasValue } from "../../shared/empty.util";
|
||||||
|
import { CacheEntry } from "./cache-entry";
|
||||||
|
|
||||||
export interface CacheableObject {
|
export interface CacheableObject {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ObjectCacheEntry {
|
export class ObjectCacheEntry implements CacheEntry {
|
||||||
data: CacheableObject;
|
data: CacheableObject;
|
||||||
timeAdded: number;
|
timeAdded: number;
|
||||||
msToLive: number;
|
msToLive: number;
|
||||||
@@ -39,7 +40,7 @@ function addToObjectCache(state: ObjectCacheState, action: AddToObjectCacheActio
|
|||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
[action.payload.objectToCache.uuid]: {
|
[action.payload.objectToCache.uuid]: {
|
||||||
data: action.payload.objectToCache,
|
data: action.payload.objectToCache,
|
||||||
timeAdded: new Date().getTime(),
|
timeAdded: action.payload.timeAdded,
|
||||||
msToLive: action.payload.msToLive
|
msToLive: action.payload.msToLive
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
12
src/app/core/cache/object-cache.service.ts
vendored
12
src/app/core/cache/object-cache.service.ts
vendored
@@ -4,6 +4,7 @@ import { ObjectCacheState, ObjectCacheEntry, CacheableObject } from "./object-ca
|
|||||||
import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from "./object-cache.actions";
|
import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from "./object-cache.actions";
|
||||||
import { Observable } from "rxjs";
|
import { Observable } from "rxjs";
|
||||||
import { hasNoValue } from "../../shared/empty.util";
|
import { hasNoValue } from "../../shared/empty.util";
|
||||||
|
import { GenericConstructor } from "../shared/generic-constructor";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ObjectCacheService {
|
export class ObjectCacheService {
|
||||||
@@ -12,22 +13,23 @@ export class ObjectCacheService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
add(objectToCache: CacheableObject, msToLive: number): void {
|
add(objectToCache: CacheableObject, msToLive: number): void {
|
||||||
this.store.dispatch(new AddToObjectCacheAction(objectToCache, msToLive));
|
this.store.dispatch(new AddToObjectCacheAction(objectToCache, new Date().getTime(), msToLive));
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(uuid: string): void {
|
remove(uuid: string): void {
|
||||||
this.store.dispatch(new RemoveFromObjectCacheAction(uuid));
|
this.store.dispatch(new RemoveFromObjectCacheAction(uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
get<T extends CacheableObject>(uuid: string): Observable<T> {
|
get<T extends CacheableObject>(uuid: string, ctor: GenericConstructor<T>): Observable<T> {
|
||||||
return this.store.select<ObjectCacheEntry>('core', 'cache', 'object', uuid)
|
return this.store.select<ObjectCacheEntry>('core', 'cache', 'object', uuid)
|
||||||
.filter(entry => this.isValid(entry))
|
.filter(entry => this.isValid(entry))
|
||||||
.map((entry: ObjectCacheEntry) => <T> entry.data);
|
.distinctUntilChanged()
|
||||||
|
.map((entry: ObjectCacheEntry) => <T> Object.assign(new ctor(), entry.data));
|
||||||
}
|
}
|
||||||
|
|
||||||
getList<T extends CacheableObject>(uuids: Array<string>): Observable<Array<T>> {
|
getList<T extends CacheableObject>(uuids: Array<string>, ctor: GenericConstructor<T>): Observable<Array<T>> {
|
||||||
return Observable.combineLatest(
|
return Observable.combineLatest(
|
||||||
uuids.map((id: string) => this.get<T>(id))
|
uuids.map((id: string) => this.get<T>(id, ctor))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
35
src/app/core/cache/request-cache.actions.ts
vendored
35
src/app/core/cache/request-cache.actions.ts
vendored
@@ -5,14 +5,15 @@ import { PaginationOptions } from "../shared/pagination-options.model";
|
|||||||
import { SortOptions } from "../shared/sort-options.model";
|
import { SortOptions } from "../shared/sort-options.model";
|
||||||
|
|
||||||
export const RequestCacheActionTypes = {
|
export const RequestCacheActionTypes = {
|
||||||
FIND_BY_ID_REQUEST: type('dspace/core/cache/request/FIND_BY_ID_REQUEST'),
|
FIND_BY_ID: type('dspace/core/cache/request/FIND_BY_ID'),
|
||||||
FIND_ALL_REQUEST: type('dspace/core/cache/request/FIND_ALL_REQUEST'),
|
FIND_ALL: type('dspace/core/cache/request/FIND_ALL'),
|
||||||
SUCCESS: type('dspace/core/cache/request/SUCCESS'),
|
SUCCESS: type('dspace/core/cache/request/SUCCESS'),
|
||||||
ERROR: type('dspace/core/cache/request/ERROR')
|
ERROR: type('dspace/core/cache/request/ERROR'),
|
||||||
|
REMOVE: type('dspace/core/cache/request/REMOVE')
|
||||||
};
|
};
|
||||||
|
|
||||||
export class FindAllRequestCacheAction implements Action {
|
export class RequestCacheFindAllAction implements Action {
|
||||||
type = RequestCacheActionTypes.FIND_ALL_REQUEST;
|
type = RequestCacheActionTypes.FIND_ALL;
|
||||||
payload: {
|
payload: {
|
||||||
key: string,
|
key: string,
|
||||||
service: OpaqueToken,
|
service: OpaqueToken,
|
||||||
@@ -38,8 +39,8 @@ export class FindAllRequestCacheAction implements Action {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FindByIDRequestCacheAction implements Action {
|
export class RequestCacheFindByIDAction implements Action {
|
||||||
type = RequestCacheActionTypes.FIND_BY_ID_REQUEST;
|
type = RequestCacheActionTypes.FIND_BY_ID;
|
||||||
payload: {
|
payload: {
|
||||||
key: string,
|
key: string,
|
||||||
service: OpaqueToken,
|
service: OpaqueToken,
|
||||||
@@ -64,13 +65,15 @@ export class RequestCacheSuccessAction implements Action {
|
|||||||
payload: {
|
payload: {
|
||||||
key: string,
|
key: string,
|
||||||
resourceUUIDs: Array<string>,
|
resourceUUIDs: Array<string>,
|
||||||
|
timeAdded: number,
|
||||||
msToLive: number
|
msToLive: number
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(key: string, resourceUUIDs: Array<string>, msToLive: number) {
|
constructor(key: string, resourceUUIDs: Array<string>, timeAdded, msToLive: number) {
|
||||||
this.payload = {
|
this.payload = {
|
||||||
key,
|
key,
|
||||||
resourceUUIDs,
|
resourceUUIDs,
|
||||||
|
timeAdded,
|
||||||
msToLive
|
msToLive
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -91,8 +94,18 @@ export class RequestCacheErrorAction implements Action {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class RequestCacheRemoveAction implements Action {
|
||||||
|
type = RequestCacheActionTypes.REMOVE;
|
||||||
|
payload: string;
|
||||||
|
|
||||||
|
constructor(key: string) {
|
||||||
|
this.payload = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type RequestCacheAction
|
export type RequestCacheAction
|
||||||
= FindAllRequestCacheAction
|
= RequestCacheFindAllAction
|
||||||
| FindByIDRequestCacheAction
|
| RequestCacheFindByIDAction
|
||||||
| RequestCacheSuccessAction
|
| RequestCacheSuccessAction
|
||||||
| RequestCacheErrorAction;
|
| RequestCacheErrorAction
|
||||||
|
| RequestCacheRemoveAction;
|
||||||
|
48
src/app/core/cache/request-cache.reducer.ts
vendored
48
src/app/core/cache/request-cache.reducer.ts
vendored
@@ -1,13 +1,17 @@
|
|||||||
import { PaginationOptions } from "../shared/pagination-options.model";
|
import { PaginationOptions } from "../shared/pagination-options.model";
|
||||||
import { SortOptions } from "../shared/sort-options.model";
|
import { SortOptions } from "../shared/sort-options.model";
|
||||||
import {
|
import {
|
||||||
RequestCacheAction, RequestCacheActionTypes, FindAllRequestCacheAction,
|
RequestCacheAction, RequestCacheActionTypes, RequestCacheFindAllAction,
|
||||||
RequestCacheSuccessAction, RequestCacheErrorAction, FindByIDRequestCacheAction
|
RequestCacheSuccessAction, RequestCacheErrorAction, RequestCacheFindByIDAction,
|
||||||
|
RequestCacheRemoveAction
|
||||||
} from "./request-cache.actions";
|
} from "./request-cache.actions";
|
||||||
import { OpaqueToken } from "@angular/core";
|
import { OpaqueToken } from "@angular/core";
|
||||||
|
import { CacheEntry } from "./cache-entry";
|
||||||
|
import { hasValue } from "../../shared/empty.util";
|
||||||
|
|
||||||
export interface CachedRequest {
|
export class RequestCacheEntry implements CacheEntry {
|
||||||
service: OpaqueToken
|
service: OpaqueToken;
|
||||||
|
key: string;
|
||||||
scopeID: string;
|
scopeID: string;
|
||||||
resourceID: string;
|
resourceID: string;
|
||||||
resourceUUIDs: Array<String>;
|
resourceUUIDs: Array<String>;
|
||||||
@@ -21,7 +25,7 @@ export interface CachedRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface RequestCacheState {
|
export interface RequestCacheState {
|
||||||
[key: string]: CachedRequest
|
[key: string]: RequestCacheEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
// Object.create(null) ensures the object has no default js properties (e.g. `__proto__`)
|
// Object.create(null) ensures the object has no default js properties (e.g. `__proto__`)
|
||||||
@@ -30,12 +34,12 @@ const initialState = Object.create(null);
|
|||||||
export const requestCacheReducer = (state = initialState, action: RequestCacheAction): RequestCacheState => {
|
export const requestCacheReducer = (state = initialState, action: RequestCacheAction): RequestCacheState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
||||||
case RequestCacheActionTypes.FIND_ALL_REQUEST: {
|
case RequestCacheActionTypes.FIND_ALL: {
|
||||||
return findAllRequest(state, <FindAllRequestCacheAction> action);
|
return findAllRequest(state, <RequestCacheFindAllAction> action);
|
||||||
}
|
}
|
||||||
|
|
||||||
case RequestCacheActionTypes.FIND_BY_ID_REQUEST: {
|
case RequestCacheActionTypes.FIND_BY_ID: {
|
||||||
return findByIDRequest(state, <FindByIDRequestCacheAction> action);
|
return findByIDRequest(state, <RequestCacheFindByIDAction> action);
|
||||||
}
|
}
|
||||||
|
|
||||||
case RequestCacheActionTypes.SUCCESS: {
|
case RequestCacheActionTypes.SUCCESS: {
|
||||||
@@ -46,15 +50,21 @@ export const requestCacheReducer = (state = initialState, action: RequestCacheAc
|
|||||||
return error(state, <RequestCacheErrorAction> action);
|
return error(state, <RequestCacheErrorAction> action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case RequestCacheActionTypes.REMOVE: {
|
||||||
|
return removeFromCache(state, <RequestCacheRemoveAction> action);
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function findAllRequest(state: RequestCacheState, action: FindAllRequestCacheAction): RequestCacheState {
|
function findAllRequest(state: RequestCacheState, action: RequestCacheFindAllAction): RequestCacheState {
|
||||||
|
console.log('break here', state);
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
[action.payload.key]: {
|
[action.payload.key]: {
|
||||||
|
key: action.payload.key,
|
||||||
service: action.payload.service,
|
service: action.payload.service,
|
||||||
scopeID: action.payload.scopeID,
|
scopeID: action.payload.scopeID,
|
||||||
resourceUUIDs: [],
|
resourceUUIDs: [],
|
||||||
@@ -66,9 +76,10 @@ function findAllRequest(state: RequestCacheState, action: FindAllRequestCacheAct
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function findByIDRequest(state: RequestCacheState, action: FindByIDRequestCacheAction): RequestCacheState {
|
function findByIDRequest(state: RequestCacheState, action: RequestCacheFindByIDAction): RequestCacheState {
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
[action.payload.key]: {
|
[action.payload.key]: {
|
||||||
|
key: action.payload.key,
|
||||||
service: action.payload.service,
|
service: action.payload.service,
|
||||||
resourceID: action.payload.resourceID,
|
resourceID: action.payload.resourceID,
|
||||||
resourceUUIDs: [],
|
resourceUUIDs: [],
|
||||||
@@ -84,7 +95,7 @@ function success(state: RequestCacheState, action: RequestCacheSuccessAction): R
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
resourceUUIDs: action.payload.resourceUUIDs,
|
resourceUUIDs: action.payload.resourceUUIDs,
|
||||||
errorMessage: undefined,
|
errorMessage: undefined,
|
||||||
timeAdded: new Date().getTime(),
|
timeAdded: action.payload.timeAdded,
|
||||||
msToLive: action.payload.msToLive
|
msToLive: action.payload.msToLive
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -99,4 +110,17 @@ function error(state: RequestCacheState, action: RequestCacheErrorAction): Reque
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeFromCache(state: RequestCacheState, action: RequestCacheRemoveAction): RequestCacheState {
|
||||||
|
if (hasValue(state[action.payload])) {
|
||||||
|
let newCache = Object.assign({}, state);
|
||||||
|
delete newCache[action.payload];
|
||||||
|
|
||||||
|
return newCache;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
73
src/app/core/cache/request-cache.service.ts
vendored
Normal file
73
src/app/core/cache/request-cache.service.ts
vendored
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { Injectable, OpaqueToken } from "@angular/core";
|
||||||
|
import { Store } from "@ngrx/store";
|
||||||
|
import { RequestCacheState, RequestCacheEntry } from "./request-cache.reducer";
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
import { hasNoValue } from "../../shared/empty.util";
|
||||||
|
import {
|
||||||
|
RequestCacheRemoveAction, RequestCacheFindAllAction,
|
||||||
|
RequestCacheFindByIDAction
|
||||||
|
} from "./request-cache.actions";
|
||||||
|
import { SortOptions } from "../shared/sort-options.model";
|
||||||
|
import { PaginationOptions } from "../shared/pagination-options.model";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RequestCacheService {
|
||||||
|
constructor(
|
||||||
|
private store: Store<RequestCacheState>
|
||||||
|
) {}
|
||||||
|
|
||||||
|
findAll(
|
||||||
|
key: string,
|
||||||
|
service: OpaqueToken,
|
||||||
|
scopeID?: string,
|
||||||
|
paginationOptions?: PaginationOptions,
|
||||||
|
sortOptions?: SortOptions
|
||||||
|
): Observable<RequestCacheEntry> {
|
||||||
|
if (!this.has(key)) {
|
||||||
|
this.store.dispatch(new RequestCacheFindAllAction(key, service, scopeID, paginationOptions, sortOptions));
|
||||||
|
}
|
||||||
|
return this.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
findById(
|
||||||
|
key: string,
|
||||||
|
service: OpaqueToken,
|
||||||
|
resourceID: string
|
||||||
|
): Observable<RequestCacheEntry> {
|
||||||
|
if (!this.has(key)) {
|
||||||
|
this.store.dispatch(new RequestCacheFindByIDAction(key, service, resourceID));
|
||||||
|
}
|
||||||
|
return this.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key: string): Observable<RequestCacheEntry> {
|
||||||
|
return this.store.select<RequestCacheEntry>('core', 'cache', 'request', key)
|
||||||
|
.filter(entry => this.isValid(entry))
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
has(key: string): boolean {
|
||||||
|
let result: boolean;
|
||||||
|
|
||||||
|
this.store.select<RequestCacheEntry>('core', 'cache', 'request', key)
|
||||||
|
.take(1)
|
||||||
|
.subscribe(entry => result = this.isValid(entry));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isValid(entry: RequestCacheEntry): boolean {
|
||||||
|
if (hasNoValue(entry)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const timeOutdated = entry.timeAdded + entry.msToLive;
|
||||||
|
const isOutDated = new Date().getTime() > timeOutdated;
|
||||||
|
if (isOutDated) {
|
||||||
|
this.store.dispatch(new RequestCacheRemoveAction(entry.key));
|
||||||
|
}
|
||||||
|
return !isOutDated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -5,6 +5,7 @@ import { isNotEmpty } from "../shared/empty.util";
|
|||||||
import { FooterComponent } from "./footer/footer.component";
|
import { FooterComponent } from "./footer/footer.component";
|
||||||
import { DSpaceRESTv2Service } from "./dspace-rest-v2/dspace-rest-v2.service";
|
import { DSpaceRESTv2Service } from "./dspace-rest-v2/dspace-rest-v2.service";
|
||||||
import { ObjectCacheService } from "./cache/object-cache.service";
|
import { ObjectCacheService } from "./cache/object-cache.service";
|
||||||
|
import { RequestCacheService } from "./cache/request-cache.service";
|
||||||
import { CollectionDataService } from "./data-services/collection-data.service";
|
import { CollectionDataService } from "./data-services/collection-data.service";
|
||||||
import { ItemDataService } from "./data-services/item-data.service";
|
import { ItemDataService } from "./data-services/item-data.service";
|
||||||
|
|
||||||
@@ -25,7 +26,8 @@ const PROVIDERS = [
|
|||||||
CollectionDataService,
|
CollectionDataService,
|
||||||
ItemDataService,
|
ItemDataService,
|
||||||
DSpaceRESTv2Service,
|
DSpaceRESTv2Service,
|
||||||
ObjectCacheService
|
ObjectCacheService,
|
||||||
|
RequestCacheService
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@@ -6,7 +6,7 @@ import { DSpaceRESTv2Serializer } from "../dspace-rest-v2/dspace-rest-v2.seriali
|
|||||||
import { ObjectCacheService } from "../cache/object-cache.service";
|
import { ObjectCacheService } from "../cache/object-cache.service";
|
||||||
import { DSpaceRESTv2Service } from "../dspace-rest-v2/dspace-rest-v2.service";
|
import { DSpaceRESTv2Service } from "../dspace-rest-v2/dspace-rest-v2.service";
|
||||||
import { Actions, Effect } from "@ngrx/effects";
|
import { Actions, Effect } from "@ngrx/effects";
|
||||||
import { FindAllRequestCacheAction, FindByIDRequestCacheAction } from "../cache/request-cache.actions";
|
import { RequestCacheFindAllAction, RequestCacheFindByIDAction } from "../cache/request-cache.actions";
|
||||||
import { CollectionDataService } from "./collection-data.service";
|
import { CollectionDataService } from "./collection-data.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -20,11 +20,11 @@ export class CollectionDataEffects extends DataEffects<Collection> {
|
|||||||
super(actions$, restApi, cache, dataService);
|
super(actions$, restApi, cache, dataService);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getFindAllEndpoint(action: FindAllRequestCacheAction): string {
|
protected getFindAllEndpoint(action: RequestCacheFindAllAction): string {
|
||||||
return '/collections';
|
return '/collections';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getFindByIdEndpoint(action: FindByIDRequestCacheAction): string {
|
protected getFindByIdEndpoint(action: RequestCacheFindByIDAction): string {
|
||||||
return `/collections/${action.payload.resourceID}`;
|
return `/collections/${action.payload.resourceID}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,19 +1,18 @@
|
|||||||
import { Injectable, OpaqueToken } from "@angular/core";
|
import { Injectable, OpaqueToken } from "@angular/core";
|
||||||
import { Store } from "@ngrx/store";
|
|
||||||
import { DataService } from "./data.service";
|
import { DataService } from "./data.service";
|
||||||
import { Collection } from "../shared/collection.model";
|
import { Collection } from "../shared/collection.model";
|
||||||
import { ObjectCacheService } from "../cache/object-cache.service";
|
import { ObjectCacheService } from "../cache/object-cache.service";
|
||||||
import { RequestCacheState } from "../cache/request-cache.reducer";
|
import { RequestCacheService } from "../cache/request-cache.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CollectionDataService extends DataService<Collection> {
|
export class CollectionDataService extends DataService<Collection> {
|
||||||
name = new OpaqueToken('CollectionDataService');
|
serviceName = new OpaqueToken('CollectionDataService');
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
store: Store<RequestCacheState>,
|
protected objectCache: ObjectCacheService,
|
||||||
cache: ObjectCacheService
|
protected requestCache: RequestCacheService,
|
||||||
) {
|
) {
|
||||||
super(store, cache);
|
super(Collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Actions, Effect } from "@ngrx/effects";
|
import { Actions } from "@ngrx/effects";
|
||||||
import { Observable } from "rxjs";
|
import { Observable } from "rxjs";
|
||||||
import { DSpaceRESTV2Response } from "../dspace-rest-v2/dspace-rest-v2-response.model";
|
import { DSpaceRESTV2Response } from "../dspace-rest-v2/dspace-rest-v2-response.model";
|
||||||
import { DSpaceRESTv2Service } from "../dspace-rest-v2/dspace-rest-v2.service";
|
import { DSpaceRESTv2Service } from "../dspace-rest-v2/dspace-rest-v2.service";
|
||||||
@@ -7,14 +7,14 @@ import { GlobalConfig } from "../../../config";
|
|||||||
import { CacheableObject } from "../cache/object-cache.reducer";
|
import { CacheableObject } from "../cache/object-cache.reducer";
|
||||||
import { Serializer } from "../serializer";
|
import { Serializer } from "../serializer";
|
||||||
import {
|
import {
|
||||||
RequestCacheActionTypes, FindAllRequestCacheAction, RequestCacheSuccessAction,
|
RequestCacheActionTypes, RequestCacheFindAllAction, RequestCacheSuccessAction,
|
||||||
RequestCacheErrorAction, FindByIDRequestCacheAction
|
RequestCacheErrorAction, RequestCacheFindByIDAction
|
||||||
} from "../cache/request-cache.actions";
|
} from "../cache/request-cache.actions";
|
||||||
import { DataService } from "./data.service";
|
import { DataService } from "./data.service";
|
||||||
|
|
||||||
export abstract class DataEffects<T extends CacheableObject> {
|
export abstract class DataEffects<T extends CacheableObject> {
|
||||||
protected abstract getFindAllEndpoint(action: FindAllRequestCacheAction): string;
|
protected abstract getFindAllEndpoint(action: RequestCacheFindAllAction): string;
|
||||||
protected abstract getFindByIdEndpoint(action: FindByIDRequestCacheAction): string;
|
protected abstract getFindByIdEndpoint(action: RequestCacheFindByIDAction): string;
|
||||||
protected abstract getSerializer(): Serializer<T>;
|
protected abstract getSerializer(): Serializer<T>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -24,13 +24,11 @@ export abstract class DataEffects<T extends CacheableObject> {
|
|||||||
private dataService: DataService<T>
|
private dataService: DataService<T>
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
// TODO, results of a findall aren't retrieved from cache for now,
|
// TODO, results of a findall aren't retrieved from cache yet
|
||||||
// because currently the cache is more of an object store. We need to move
|
|
||||||
// more towards memoization for things like this.
|
|
||||||
protected findAll = this.actions$
|
protected findAll = this.actions$
|
||||||
.ofType(RequestCacheActionTypes.FIND_ALL_REQUEST)
|
.ofType(RequestCacheActionTypes.FIND_ALL)
|
||||||
.filter((action: FindAllRequestCacheAction) => action.payload.service === this.dataService.name)
|
.filter((action: RequestCacheFindAllAction) => action.payload.service === this.dataService.serviceName)
|
||||||
.flatMap((action: FindAllRequestCacheAction) => {
|
.flatMap((action: RequestCacheFindAllAction) => {
|
||||||
//TODO scope, pagination, sorting -> when we know how that works in rest
|
//TODO scope, pagination, sorting -> when we know how that works in rest
|
||||||
return this.restApi.get(this.getFindAllEndpoint(action))
|
return this.restApi.get(this.getFindAllEndpoint(action))
|
||||||
.map((data: DSpaceRESTV2Response) => this.getSerializer().deserializeArray(data))
|
.map((data: DSpaceRESTV2Response) => this.getSerializer().deserializeArray(data))
|
||||||
@@ -40,20 +38,20 @@ export abstract class DataEffects<T extends CacheableObject> {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.map((ts: Array<T>) => ts.map(t => t.uuid))
|
.map((ts: Array<T>) => ts.map(t => t.uuid))
|
||||||
.map((ids: Array<string>) => new RequestCacheSuccessAction(action.payload.key, ids, GlobalConfig.cache.msToLive))
|
.map((ids: Array<string>) => new RequestCacheSuccessAction(action.payload.key, ids, new Date().getTime(), GlobalConfig.cache.msToLive))
|
||||||
.catch((errorMsg: string) => Observable.of(new RequestCacheErrorAction(action.payload.key, errorMsg)));
|
.catch((errorMsg: string) => Observable.of(new RequestCacheErrorAction(action.payload.key, errorMsg)));
|
||||||
});
|
});
|
||||||
|
|
||||||
protected findById = this.actions$
|
protected findById = this.actions$
|
||||||
.ofType(RequestCacheActionTypes.FIND_BY_ID_REQUEST)
|
.ofType(RequestCacheActionTypes.FIND_BY_ID)
|
||||||
.filter((action: FindAllRequestCacheAction) => action.payload.service === this.dataService.name)
|
.filter((action: RequestCacheFindAllAction) => action.payload.service === this.dataService.serviceName)
|
||||||
.flatMap((action: FindByIDRequestCacheAction) => {
|
.flatMap((action: RequestCacheFindByIDAction) => {
|
||||||
return this.restApi.get(this.getFindByIdEndpoint(action))
|
return this.restApi.get(this.getFindByIdEndpoint(action))
|
||||||
.map((data: DSpaceRESTV2Response) => this.getSerializer().deserialize(data))
|
.map((data: DSpaceRESTV2Response) => this.getSerializer().deserialize(data))
|
||||||
.do((t: T) => {
|
.do((t: T) => {
|
||||||
this.objectCache.add(t, GlobalConfig.cache.msToLive);
|
this.objectCache.add(t, GlobalConfig.cache.msToLive);
|
||||||
})
|
})
|
||||||
.map((t: T) => new RequestCacheSuccessAction(action.payload.key, [t.uuid], GlobalConfig.cache.msToLive))
|
.map((t: T) => new RequestCacheSuccessAction(action.payload.key, [t.uuid], new Date().getTime(), GlobalConfig.cache.msToLive))
|
||||||
.catch((errorMsg: string) => Observable.of(new RequestCacheErrorAction(action.payload.key, errorMsg)));
|
.catch((errorMsg: string) => Observable.of(new RequestCacheErrorAction(action.payload.key, errorMsg)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,39 +1,39 @@
|
|||||||
import { OpaqueToken } from "@angular/core";
|
import { OpaqueToken } from "@angular/core";
|
||||||
import { Observable } from "rxjs";
|
import { Observable } from "rxjs";
|
||||||
import { Store } from "@ngrx/store";
|
|
||||||
import { ObjectCacheService } from "../cache/object-cache.service";
|
import { ObjectCacheService } from "../cache/object-cache.service";
|
||||||
|
import { RequestCacheService } from "../cache/request-cache.service";
|
||||||
import { CacheableObject } from "../cache/object-cache.reducer";
|
import { CacheableObject } from "../cache/object-cache.reducer";
|
||||||
import { RequestCacheState } from "../cache/request-cache.reducer";
|
|
||||||
import { FindAllRequestCacheAction, FindByIDRequestCacheAction } from "../cache/request-cache.actions";
|
|
||||||
import { ParamHash } from "../shared/param-hash";
|
import { ParamHash } from "../shared/param-hash";
|
||||||
import { isNotEmpty } from "../../shared/empty.util";
|
import { isNotEmpty } from "../../shared/empty.util";
|
||||||
|
import { GenericConstructor } from "../shared/generic-constructor";
|
||||||
|
|
||||||
export abstract class DataService<T extends CacheableObject> {
|
export abstract class DataService<T extends CacheableObject> {
|
||||||
abstract name: OpaqueToken;
|
abstract serviceName: OpaqueToken;
|
||||||
|
protected abstract objectCache: ObjectCacheService;
|
||||||
|
protected abstract requestCache: RequestCacheService;
|
||||||
|
|
||||||
constructor(
|
constructor(private modelType: GenericConstructor<T>) {
|
||||||
private store: Store<RequestCacheState>,
|
|
||||||
private objectCache: ObjectCacheService
|
}
|
||||||
) { }
|
|
||||||
|
|
||||||
findAll(scopeID?: string): Observable<Array<T>> {
|
findAll(scopeID?: string): Observable<Array<T>> {
|
||||||
const key = new ParamHash(this.name, 'findAll', scopeID).toString();
|
const key = new ParamHash(this.serviceName, 'findAll', scopeID).toString();
|
||||||
this.store.dispatch(new FindAllRequestCacheAction(key, this.name, scopeID));
|
return this.requestCache.findAll(key, this.serviceName, scopeID)
|
||||||
//get an observable of the IDs from the store
|
//get an observable of the IDs from the RequestCache
|
||||||
return this.store.select<Array<string>>('core', 'cache', 'request', key, 'resourceUUIDs')
|
.map(entry => entry.resourceUUIDs)
|
||||||
.flatMap((resourceUUIDs: Array<string>) => {
|
.flatMap((resourceUUIDs: Array<string>) => {
|
||||||
// use those IDs to fetch the actual objects from the cache
|
// use those IDs to fetch the actual objects from the ObjectCache
|
||||||
return this.objectCache.getList<T>(resourceUUIDs);
|
return this.objectCache.getList<T>(resourceUUIDs, this.modelType);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
findById(id: string): Observable<T> {
|
findById(id: string): Observable<T> {
|
||||||
const key = new ParamHash(this.name, 'findById', id).toString();
|
const key = new ParamHash(this.serviceName, 'findById', id).toString();
|
||||||
this.store.dispatch(new FindByIDRequestCacheAction(key, this.name, id));
|
return this.requestCache.findById(key, this.serviceName, id)
|
||||||
return this.store.select<Array<string>>('core', 'cache', 'request', key, 'resourceUUIDs')
|
.map(entry => entry.resourceUUIDs)
|
||||||
.flatMap((resourceUUIDs: Array<string>) => {
|
.flatMap((resourceUUIDs: Array<string>) => {
|
||||||
if(isNotEmpty(resourceUUIDs)) {
|
if(isNotEmpty(resourceUUIDs)) {
|
||||||
return this.objectCache.get<T>(resourceUUIDs[0]);
|
return this.objectCache.get<T>(resourceUUIDs[0], this.modelType);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return Observable.of(undefined);
|
return Observable.of(undefined);
|
||||||
|
@@ -6,7 +6,7 @@ import { DSpaceRESTv2Serializer } from "../dspace-rest-v2/dspace-rest-v2.seriali
|
|||||||
import { ObjectCacheService } from "../cache/object-cache.service";
|
import { ObjectCacheService } from "../cache/object-cache.service";
|
||||||
import { DSpaceRESTv2Service } from "../dspace-rest-v2/dspace-rest-v2.service";
|
import { DSpaceRESTv2Service } from "../dspace-rest-v2/dspace-rest-v2.service";
|
||||||
import { Actions, Effect } from "@ngrx/effects";
|
import { Actions, Effect } from "@ngrx/effects";
|
||||||
import { FindAllRequestCacheAction, FindByIDRequestCacheAction } from "../cache/request-cache.actions";
|
import { RequestCacheFindAllAction, RequestCacheFindByIDAction } from "../cache/request-cache.actions";
|
||||||
import { ItemDataService } from "./item-data.service";
|
import { ItemDataService } from "./item-data.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -20,11 +20,11 @@ export class ItemDataEffects extends DataEffects<Item> {
|
|||||||
super(actions$, restApi, cache, dataService);
|
super(actions$, restApi, cache, dataService);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getFindAllEndpoint(action: FindAllRequestCacheAction): string {
|
protected getFindAllEndpoint(action: RequestCacheFindAllAction): string {
|
||||||
return '/items';
|
return '/items';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getFindByIdEndpoint(action: FindByIDRequestCacheAction): string {
|
protected getFindByIdEndpoint(action: RequestCacheFindByIDAction): string {
|
||||||
return `/items/${action.payload.resourceID}`;
|
return `/items/${action.payload.resourceID}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,19 +1,18 @@
|
|||||||
import { Injectable, OpaqueToken } from "@angular/core";
|
import { Injectable, OpaqueToken } from "@angular/core";
|
||||||
import { Store } from "@ngrx/store";
|
|
||||||
import { DataService } from "./data.service";
|
import { DataService } from "./data.service";
|
||||||
import { Item } from "../shared/item.model";
|
import { Item } from "../shared/item.model";
|
||||||
import { ObjectCacheService } from "../cache/object-cache.service";
|
import { ObjectCacheService } from "../cache/object-cache.service";
|
||||||
import { RequestCacheState } from "../cache/request-cache.reducer";
|
import { RequestCacheService } from "../cache/request-cache.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ItemDataService extends DataService<Item> {
|
export class ItemDataService extends DataService<Item> {
|
||||||
name = new OpaqueToken('ItemDataService');
|
serviceName = new OpaqueToken('ItemDataService');
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
store: Store<RequestCacheState>,
|
protected objectCache: ObjectCacheService,
|
||||||
cache: ObjectCacheService
|
protected requestCache: RequestCacheService,
|
||||||
) {
|
) {
|
||||||
super(store, cache);
|
super(Item);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -2,13 +2,7 @@ import { Serialize, Deserialize } from "cerialize";
|
|||||||
import { Serializer } from "../serializer";
|
import { Serializer } from "../serializer";
|
||||||
import { DSpaceRESTV2Response } from "./dspace-rest-v2-response.model";
|
import { DSpaceRESTV2Response } from "./dspace-rest-v2-response.model";
|
||||||
import { DSpaceRESTv2Validator } from "./dspace-rest-v2.validator";
|
import { DSpaceRESTv2Validator } from "./dspace-rest-v2.validator";
|
||||||
|
import { GenericConstructor } from "../shared/generic-constructor";
|
||||||
/**
|
|
||||||
* ensures we can use 'typeof T' as a type
|
|
||||||
* more details:
|
|
||||||
* https://github.com/Microsoft/TypeScript/issues/204#issuecomment-257722306
|
|
||||||
*/
|
|
||||||
type Constructor<T> = { new (...args: any[]): T } | ((...args: any[]) => T) | Function;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This Serializer turns responses from v2 of DSpace's REST API
|
* This Serializer turns responses from v2 of DSpace's REST API
|
||||||
@@ -22,7 +16,7 @@ export class DSpaceRESTv2Serializer<T> implements Serializer<T> {
|
|||||||
* @param modelType a class or interface to indicate
|
* @param modelType a class or interface to indicate
|
||||||
* the kind of model this serializer should work with
|
* the kind of model this serializer should work with
|
||||||
*/
|
*/
|
||||||
constructor(private modelType: Constructor<T>) {
|
constructor(private modelType: GenericConstructor<T>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -5,18 +5,19 @@ import { Item } from "./item.model";
|
|||||||
|
|
||||||
@inheritSerialization(DSpaceObject)
|
@inheritSerialization(DSpaceObject)
|
||||||
export class Bundle extends DSpaceObject {
|
export class Bundle extends DSpaceObject {
|
||||||
/**
|
/**
|
||||||
* The primary bitstream of this Bundle
|
* The primary bitstream of this Bundle
|
||||||
*/
|
*/
|
||||||
primaryBitstream: Bitstream;
|
primaryBitstream: Bitstream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of Items that are direct parents of this Bundle
|
* An array of Items that are direct parents of this Bundle
|
||||||
*/
|
*/
|
||||||
parents: Array<Item>;
|
parents: Array<Item>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Item that owns this Bundle
|
||||||
|
*/
|
||||||
|
owner: Item;
|
||||||
|
|
||||||
/**
|
|
||||||
* The Item that owns this Bundle
|
|
||||||
*/
|
|
||||||
owner: Item;
|
|
||||||
}
|
}
|
||||||
|
@@ -8,44 +8,44 @@ import { CacheableObject } from "../cache/object-cache.reducer";
|
|||||||
*/
|
*/
|
||||||
export abstract class DSpaceObject implements CacheableObject {
|
export abstract class DSpaceObject implements CacheableObject {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The human-readable identifier of this DSpaceObject
|
* The human-readable identifier of this DSpaceObject
|
||||||
*/
|
*/
|
||||||
@autoserialize
|
@autoserialize
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The universally unique identifier of this DSpaceObject
|
* The universally unique identifier of this DSpaceObject
|
||||||
*/
|
*/
|
||||||
@autoserialize
|
@autoserialize
|
||||||
uuid: string;
|
uuid: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A string representing the kind of DSpaceObject, e.g. community, item, …
|
* A string representing the kind of DSpaceObject, e.g. community, item, …
|
||||||
*/
|
*/
|
||||||
type: string;
|
type: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name for this DSpaceObject
|
* The name for this DSpaceObject
|
||||||
*/
|
*/
|
||||||
@autoserialize
|
@autoserialize
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array containing all metadata of this DSpaceObject
|
* An array containing all metadata of this DSpaceObject
|
||||||
*/
|
*/
|
||||||
@autoserializeAs(Metadatum)
|
@autoserializeAs(Metadatum)
|
||||||
metadata: Array<Metadatum>;
|
metadata: Array<Metadatum>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of DSpaceObjects that are direct parents of this DSpaceObject
|
* An array of DSpaceObjects that are direct parents of this DSpaceObject
|
||||||
*/
|
*/
|
||||||
parents: Array<DSpaceObject>;
|
parents: Array<DSpaceObject>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The DSpaceObject that owns this DSpaceObject
|
* The DSpaceObject that owns this DSpaceObject
|
||||||
*/
|
*/
|
||||||
owner: DSpaceObject;
|
owner: DSpaceObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a metadata field by key and language
|
* Find a metadata field by key and language
|
||||||
@@ -58,17 +58,17 @@ export abstract class DSpaceObject implements CacheableObject {
|
|||||||
* @param language
|
* @param language
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
findMetadata(key: string, language?: string): string {
|
findMetadata(key: string, language?: string): string {
|
||||||
const metadatum = this.metadata
|
const metadatum = this.metadata
|
||||||
.find((metadatum: Metadatum) => {
|
.find((metadatum: Metadatum) => {
|
||||||
return metadatum.key === key &&
|
return metadatum.key === key &&
|
||||||
(isEmpty(language) || metadatum.language === language)
|
(isEmpty(language) || metadatum.language === language)
|
||||||
});
|
});
|
||||||
if (isNotEmpty(metadatum)) {
|
if (isNotEmpty(metadatum)) {
|
||||||
return metadatum.value;
|
return metadatum.value;
|
||||||
}
|
|
||||||
else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
7
src/app/core/shared/generic-constructor.ts
Normal file
7
src/app/core/shared/generic-constructor.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* ensures we can use 'typeof T' as a type
|
||||||
|
* more details:
|
||||||
|
* https://github.com/Microsoft/TypeScript/issues/204#issuecomment-257722306
|
||||||
|
*/
|
||||||
|
export type GenericConstructor<T> = { new (...args: any[]): T };
|
||||||
|
|
@@ -5,34 +5,35 @@ import { Collection } from "./collection.model";
|
|||||||
@inheritSerialization(DSpaceObject)
|
@inheritSerialization(DSpaceObject)
|
||||||
export class Item extends DSpaceObject {
|
export class Item extends DSpaceObject {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A string representing the unique handle of this Item
|
* A string representing the unique handle of this Item
|
||||||
*/
|
*/
|
||||||
@autoserialize
|
@autoserialize
|
||||||
handle: string;
|
handle: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Date of the last modification of this Item
|
* The Date of the last modification of this Item
|
||||||
*/
|
*/
|
||||||
lastModified: Date;
|
lastModified: Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A boolean representing if this Item is currently archived or not
|
* A boolean representing if this Item is currently archived or not
|
||||||
*/
|
*/
|
||||||
isArchived: boolean;
|
isArchived: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A boolean representing if this Item is currently withdrawn or not
|
* A boolean representing if this Item is currently withdrawn or not
|
||||||
*/
|
*/
|
||||||
isWithdrawn: boolean;
|
isWithdrawn: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of Collections that are direct parents of this Item
|
* An array of Collections that are direct parents of this Item
|
||||||
*/
|
*/
|
||||||
parents: Array<Collection>;
|
parents: Array<Collection>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Collection that owns this Item
|
||||||
|
*/
|
||||||
|
owner: Collection;
|
||||||
|
|
||||||
/**
|
|
||||||
* The Collection that owns this Item
|
|
||||||
*/
|
|
||||||
owner: Collection;
|
|
||||||
}
|
}
|
||||||
|
@@ -1,88 +0,0 @@
|
|||||||
import { Inject, Injectable, isDevMode } from '@angular/core';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class DemoCacheService {
|
|
||||||
static KEY = 'DemoCacheService';
|
|
||||||
|
|
||||||
constructor( @Inject('LRU') public _cache: Map<string, any>) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* check if there is a value in our store
|
|
||||||
*/
|
|
||||||
has(key: string | number): boolean {
|
|
||||||
let _key = this.normalizeKey(key);
|
|
||||||
return this._cache.has(_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* store our state
|
|
||||||
*/
|
|
||||||
set(key: string | number, value: any): void {
|
|
||||||
let _key = this.normalizeKey(key);
|
|
||||||
this._cache.set(_key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get our cached value
|
|
||||||
*/
|
|
||||||
get(key: string | number): any {
|
|
||||||
let _key = this.normalizeKey(key);
|
|
||||||
return this._cache.get(_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* release memory refs
|
|
||||||
*/
|
|
||||||
clear(): void {
|
|
||||||
this._cache.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* convert to json for the client
|
|
||||||
*/
|
|
||||||
dehydrate(): any {
|
|
||||||
let json = {};
|
|
||||||
this._cache.forEach((value: any, key: string) => json[key] = value);
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* convert server json into out initial state
|
|
||||||
*/
|
|
||||||
rehydrate(json: any): void {
|
|
||||||
Object.keys(json).forEach((key: string) => {
|
|
||||||
let _key = this.normalizeKey(key);
|
|
||||||
let value = json[_key];
|
|
||||||
this._cache.set(_key, value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* allow JSON.stringify to work
|
|
||||||
*/
|
|
||||||
toJSON(): any {
|
|
||||||
return this.dehydrate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* convert numbers into strings
|
|
||||||
*/
|
|
||||||
normalizeKey(key: string | number): string {
|
|
||||||
if (isDevMode() && this._isInvalidValue(key)) {
|
|
||||||
throw new Error('Please provide a valid key to save in the DemoCacheService');
|
|
||||||
}
|
|
||||||
|
|
||||||
return key + '';
|
|
||||||
}
|
|
||||||
|
|
||||||
_isInvalidValue(key): boolean {
|
|
||||||
return key === null ||
|
|
||||||
key === undefined ||
|
|
||||||
key === 0 ||
|
|
||||||
key === '' ||
|
|
||||||
typeof key === 'boolean' ||
|
|
||||||
Number.isNaN(<number>key);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,55 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import 'rxjs/add/observable/of';
|
|
||||||
import 'rxjs/add/operator/do';
|
|
||||||
import 'rxjs/add/operator/share';
|
|
||||||
|
|
||||||
import { DemoCacheService } from '../demo-cache.service';
|
|
||||||
import { ApiService } from '../api.service';
|
|
||||||
|
|
||||||
export function hashCodeString(str: string): string {
|
|
||||||
let hash = 0;
|
|
||||||
if (str.length === 0) {
|
|
||||||
return hash + '';
|
|
||||||
}
|
|
||||||
for (let i = 0; i < str.length; i++) {
|
|
||||||
let char = str.charCodeAt(i);
|
|
||||||
hash = ((hash << 5) - hash) + char;
|
|
||||||
hash = hash & hash; // Convert to 32bit integer
|
|
||||||
}
|
|
||||||
return hash + '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// domain/feature service
|
|
||||||
@Injectable()
|
|
||||||
export class ModelService {
|
|
||||||
// This is only one example of one Model depending on your domain
|
|
||||||
constructor(public _api: ApiService, public _cache: DemoCacheService) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* whatever domain/feature method name
|
|
||||||
*/
|
|
||||||
get(url) {
|
|
||||||
// you want to return the cache if there is a response in it.
|
|
||||||
// This would cache the first response so if your API isn't idempotent
|
|
||||||
// you probably want to remove the item from the cache after you use it. LRU of 10
|
|
||||||
// you can use also hashCodeString here
|
|
||||||
let key = url;
|
|
||||||
|
|
||||||
if (this._cache.has(key)) {
|
|
||||||
return Observable.of(this._cache.get(key));
|
|
||||||
}
|
|
||||||
// you probably shouldn't .share() and you should write the correct logic
|
|
||||||
return this._api.get(url)
|
|
||||||
.do(json => {
|
|
||||||
this._cache.set(key, json);
|
|
||||||
})
|
|
||||||
.share();
|
|
||||||
}
|
|
||||||
// don't cache here since we're creating
|
|
||||||
create() {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
|
@@ -7,7 +7,6 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
|||||||
import { TranslateModule } from 'ng2-translate/ng2-translate';
|
import { TranslateModule } from 'ng2-translate/ng2-translate';
|
||||||
|
|
||||||
import { ApiService } from './api.service';
|
import { ApiService } from './api.service';
|
||||||
import { ModelService } from './model/model.service';
|
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||||
@@ -28,7 +27,6 @@ const COMPONENTS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const PROVIDERS = [
|
const PROVIDERS = [
|
||||||
ModelService,
|
|
||||||
ApiService
|
ApiService
|
||||||
];
|
];
|
||||||
|
|
||||||
|
16
src/app/store.actions.ts
Normal file
16
src/app/store.actions.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { type } from "./shared/ngrx/type";
|
||||||
|
import { Action } from "@ngrx/store";
|
||||||
|
import { AppState } from "./app.reducers";
|
||||||
|
|
||||||
|
export const StoreActionTypes = {
|
||||||
|
REHYDRATE: type('dspace/ngrx/rehydrate')
|
||||||
|
};
|
||||||
|
|
||||||
|
export class RehydrateStoreAction implements Action {
|
||||||
|
type = StoreActionTypes.REHYDRATE;
|
||||||
|
|
||||||
|
constructor(public payload: AppState) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StoreAction
|
||||||
|
= RehydrateStoreAction;
|
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
|
|||||||
import { Http } from '@angular/http';
|
import { Http } from '@angular/http';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { UniversalModule, isBrowser, isNode, AUTO_PREBOOT } from 'angular2-universal/browser'; // for AoT we need to manually split universal packages
|
import { UniversalModule, isBrowser, isNode } from 'angular2-universal/browser'; // for AoT we need to manually split universal packages
|
||||||
import { IdlePreload, IdlePreloadModule } from '@angularclass/idle-preload';
|
import { IdlePreload, IdlePreloadModule } from '@angularclass/idle-preload';
|
||||||
|
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
@@ -10,12 +10,18 @@ import { TranslateLoader, TranslateModule, TranslateStaticLoader } from 'ng2-tra
|
|||||||
|
|
||||||
import { AppModule, AppComponent } from './app/app.module';
|
import { AppModule, AppComponent } from './app/app.module';
|
||||||
import { SharedModule } from './app/shared/shared.module';
|
import { SharedModule } from './app/shared/shared.module';
|
||||||
import { DemoCacheService } from './app/shared/demo-cache.service';
|
|
||||||
import { CoreModule } from "./app/core/core.module";
|
import { CoreModule } from "./app/core/core.module";
|
||||||
|
|
||||||
|
import { StoreModule, Store } from "@ngrx/store";
|
||||||
|
import { RouterStoreModule } from "@ngrx/router-store";
|
||||||
|
import { StoreDevtoolsModule } from "@ngrx/store-devtools";
|
||||||
|
import { rootReducer, NGRX_CACHE_KEY, AppState } from './app/app.reducers';
|
||||||
|
import { effects } from './app/app.effects';
|
||||||
|
|
||||||
// Will be merged into @angular/platform-browser in a later release
|
// Will be merged into @angular/platform-browser in a later release
|
||||||
// see https://github.com/angular/angular/pull/12322
|
// see https://github.com/angular/angular/pull/12322
|
||||||
import { Meta } from './angular2-meta';
|
import { Meta } from './angular2-meta';
|
||||||
|
import { RehydrateStoreAction } from "./app/store.actions";
|
||||||
|
|
||||||
// import * as LRU from 'modern-lru';
|
// import * as LRU from 'modern-lru';
|
||||||
|
|
||||||
@@ -38,7 +44,6 @@ export function getResponse() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO(gdi2290): refactor into Universal
|
|
||||||
export const UNIVERSAL_KEY = 'UNIVERSAL_CACHE';
|
export const UNIVERSAL_KEY = 'UNIVERSAL_CACHE';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -60,6 +65,10 @@ export const UNIVERSAL_KEY = 'UNIVERSAL_CACHE';
|
|||||||
CoreModule.forRoot(),
|
CoreModule.forRoot(),
|
||||||
SharedModule,
|
SharedModule,
|
||||||
AppModule,
|
AppModule,
|
||||||
|
StoreModule.provideStore(rootReducer),
|
||||||
|
RouterStoreModule.connectRouter(),
|
||||||
|
StoreDevtoolsModule.instrumentOnlyWithExtension(),
|
||||||
|
effects
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: 'isBrowser', useValue: isBrowser },
|
{ provide: 'isBrowser', useValue: isBrowser },
|
||||||
@@ -70,23 +79,21 @@ export const UNIVERSAL_KEY = 'UNIVERSAL_CACHE';
|
|||||||
|
|
||||||
{ provide: 'LRU', useFactory: getLRU, deps: [] },
|
{ provide: 'LRU', useFactory: getLRU, deps: [] },
|
||||||
|
|
||||||
DemoCacheService,
|
|
||||||
|
|
||||||
Meta,
|
Meta,
|
||||||
|
|
||||||
// { provide: AUTO_PREBOOT, useValue: false } // turn off auto preboot complete
|
// { provide: AUTO_PREBOOT, useValue: false } // turn off auto preboot complete
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class MainModule {
|
export class MainModule {
|
||||||
constructor(public cache: DemoCacheService) {
|
constructor(public store: Store<AppState>) {
|
||||||
// TODO(gdi2290): refactor into a lifecycle hook
|
// TODO(gdi2290): refactor into a lifecycle hook
|
||||||
this.doRehydrate();
|
this.doRehydrate();
|
||||||
}
|
}
|
||||||
|
|
||||||
doRehydrate() {
|
doRehydrate() {
|
||||||
let defaultValue = {};
|
let defaultValue = {};
|
||||||
let serverCache = this._getCacheValue(DemoCacheService.KEY, defaultValue);
|
let serverCache = this._getCacheValue(NGRX_CACHE_KEY, defaultValue);
|
||||||
this.cache.rehydrate(serverCache);
|
this.store.dispatch(new RehydrateStoreAction(serverCache));
|
||||||
}
|
}
|
||||||
|
|
||||||
_getCacheValue(key: string, defaultValue: any): any {
|
_getCacheValue(key: string, defaultValue: any): any {
|
||||||
@@ -95,7 +102,7 @@ export class MainModule {
|
|||||||
if (win[UNIVERSAL_KEY] && win[UNIVERSAL_KEY][key]) {
|
if (win[UNIVERSAL_KEY] && win[UNIVERSAL_KEY][key]) {
|
||||||
let serverCache = defaultValue;
|
let serverCache = defaultValue;
|
||||||
try {
|
try {
|
||||||
serverCache = JSON.parse(win[UNIVERSAL_KEY][key]);
|
serverCache = win[UNIVERSAL_KEY][key];
|
||||||
if (typeof serverCache !== typeof defaultValue) {
|
if (typeof serverCache !== typeof defaultValue) {
|
||||||
console.log('Angular Universal: The type of data from the server is different from the default value type');
|
console.log('Angular Universal: The type of data from the server is different from the default value type');
|
||||||
serverCache = defaultValue;
|
serverCache = defaultValue;
|
||||||
|
@@ -9,9 +9,14 @@ import { TranslateLoader, TranslateModule, TranslateStaticLoader } from 'ng2-tra
|
|||||||
|
|
||||||
import { AppModule, AppComponent } from './app/app.module';
|
import { AppModule, AppComponent } from './app/app.module';
|
||||||
import { SharedModule } from './app/shared/shared.module';
|
import { SharedModule } from './app/shared/shared.module';
|
||||||
import { DemoCacheService } from './app/shared/demo-cache.service';
|
|
||||||
import { CoreModule } from "./app/core/core.module";
|
import { CoreModule } from "./app/core/core.module";
|
||||||
|
|
||||||
|
import { StoreModule, Store } from "@ngrx/store";
|
||||||
|
import { RouterStoreModule } from "@ngrx/router-store";
|
||||||
|
import { StoreDevtoolsModule } from "@ngrx/store-devtools";
|
||||||
|
import { rootReducer, AppState, NGRX_CACHE_KEY } from './app/app.reducers';
|
||||||
|
import { effects } from './app/app.effects';
|
||||||
|
|
||||||
// Will be merged into @angular/platform-browser in a later release
|
// Will be merged into @angular/platform-browser in a later release
|
||||||
// see https://github.com/angular/angular/pull/12322
|
// see https://github.com/angular/angular/pull/12322
|
||||||
import { Meta } from './angular2-meta';
|
import { Meta } from './angular2-meta';
|
||||||
@@ -30,7 +35,6 @@ export function getResponse() {
|
|||||||
return Zone.current.get('res') || {};
|
return Zone.current.get('res') || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(gdi2290): refactor into Universal
|
|
||||||
export const UNIVERSAL_KEY = 'UNIVERSAL_CACHE';
|
export const UNIVERSAL_KEY = 'UNIVERSAL_CACHE';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -51,6 +55,10 @@ export const UNIVERSAL_KEY = 'UNIVERSAL_CACHE';
|
|||||||
CoreModule.forRoot(),
|
CoreModule.forRoot(),
|
||||||
SharedModule,
|
SharedModule,
|
||||||
AppModule,
|
AppModule,
|
||||||
|
StoreModule.provideStore(rootReducer),
|
||||||
|
RouterStoreModule.connectRouter(),
|
||||||
|
StoreDevtoolsModule.instrumentOnlyWithExtension(),
|
||||||
|
effects
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: 'isBrowser', useValue: isBrowser },
|
{ provide: 'isBrowser', useValue: isBrowser },
|
||||||
@@ -61,13 +69,11 @@ export const UNIVERSAL_KEY = 'UNIVERSAL_CACHE';
|
|||||||
|
|
||||||
{ provide: 'LRU', useFactory: getLRU, deps: [] },
|
{ provide: 'LRU', useFactory: getLRU, deps: [] },
|
||||||
|
|
||||||
DemoCacheService,
|
|
||||||
|
|
||||||
Meta,
|
Meta,
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class MainModule {
|
export class MainModule {
|
||||||
constructor(public cache: DemoCacheService) {
|
constructor(public store: Store<AppState>) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,14 +82,17 @@ export class MainModule {
|
|||||||
* in Universal for now until it's fixed
|
* in Universal for now until it's fixed
|
||||||
*/
|
*/
|
||||||
universalDoDehydrate = (universalCache) => {
|
universalDoDehydrate = (universalCache) => {
|
||||||
universalCache[DemoCacheService.KEY] = JSON.stringify(this.cache.dehydrate());
|
this.store.take(1).subscribe(state => {
|
||||||
}
|
universalCache[NGRX_CACHE_KEY] = state;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the cache after it's rendered
|
* Clear the cache after it's rendered
|
||||||
*/
|
*/
|
||||||
universalAfterDehydrate = () => {
|
universalAfterDehydrate = () => {
|
||||||
// comment out if LRU provided at platform level to be shared between each user
|
// comment out if LRU provided at platform level to be shared between each user
|
||||||
this.cache.clear();
|
// this.cache.clear();
|
||||||
|
//TODO is this necessary in dspace's case?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user