Commit 569cffee by Tuukka Kivilahti

cache and viplist using that cache

1 parent 3059932f
# devausnoteja (tmp ohjeet)
## Muistiinpanoja
## Devaus
### Kääntö jonnekkin:
`ng build --output-path=../moya-ear/target/moya-ear-1.2-SNAPSHOT/moya-web-1.2-SNAPSHOT.war/resources/angular-dist/`
### devaus workflow
Kun ei tarvitse tesmailla menuja, on nopeinta ajaa lasikalaa erikseen, ja erikseen ajaa webpack -palvelinta, joka palvelee angularia.
moyaproxy.conf.json sisältää proxyasetukset porttiin 8080. Käyttö:
`ng serve --proxy-config=moyaproxy.conf.json`
Tämän jälkeen kirjaa selaimesi sisään moyaan: http://localhost:4200/MoyaWeb/
### HMR
Hot Module replacement.
Varsianista hyötyä en tiiä, joku(tm) voisi vertailla onko nopeampi ajaa tällä vai ei. Voi myös olla ettei toimi vielä täysin. Otettu käyttöön ohjeiden mukaan: https://github.com/jschwarty/angularcli-hmr-example
`ng serve --hmr -e=hmr`
## Koodijuttuja
### moya-rest
......@@ -16,6 +37,7 @@ Jos tulee muita järkeviä kokonaisuuksia, joita voi käyttää muualla, jaa oma
## TODO
Tekemistä jota voi esim kämpissä säätää
......
......@@ -25,6 +25,7 @@
"environments": {
"source": "environments/environment.ts",
"dev": "environments/environment.ts",
"hmr": "environments/environment.hmr.ts",
"prod": "environments/environment.prod.ts"
}
}
......
{
"/MoyaWeb": {
"target": "http://localhost:8080",
"secure": false
}
}
......@@ -4,9 +4,6 @@
"license": "MIT",
"angular-cli": {},
"scripts": {
"prekissa": "echo \"pre kissa\" ",
"postkissa": "echo \"post kissa\" ",
"kissa": "node test.js"
},
"private": true,
"dependencies": {
......@@ -29,6 +26,7 @@
},
"devDependencies": {
"@angular/compiler-cli": "^2.3.1",
"@angularclass/hmr": "^1.2.2",
"@types/jasmine": "2.5.38",
"@types/node": "^6.0.42",
"angular-cli": "1.0.0-beta.24",
......
<router-outlet></router-outlet>
<a routerLink="/test" routerLinkActive="active">test</a>
<a routerLink="/vip/viplist" routerLinkActive="active">viplist</a>
/**
* Created by tuukka on 15/02/17.
*/
// vim magic: %s/^\s*\w\+\s\+\(\w\+\)\s\+\(\w\+\)\s*.*;$/ \2: \L\1;/
export enum UserGender {
MALE,
FEMALE,
UNSPECIFIED
}
export class User {
nick: string;
login: string;
eventuserId: number;
userId: number;
firstname: string;
lastname: string;
password: string;
birthday: Date;
gender: UserGender;
phoneNumber: string;
email: string;
streetAddress: string;
zipCode: string;
postOffice: string;
constructor() { }
}
import {VipProduct} from "./vip-product.model";
import {User} from "./user.model";
/**
* Created by tuukka on 04/02/17.
*/
......@@ -17,7 +18,7 @@ export class Vip {
public Integer creatorId;
public Integer hostId;
public List<VipProductPojo> products = new ArrayList<>();
*/
*/
id: number;
description: string;
......@@ -25,11 +26,12 @@ export class Vip {
created: Date;
eventuserId: number;
creatorId: number;
hostId: number;
products: VipProduct[];
host: User;
products: VipProduct[];
constructor() {}
constructor() {
}
}
......@@ -3,6 +3,8 @@ import { CommonModule } from '@angular/common';
import {HttpModule} from "@angular/http";
import {MoyaRestService} from "./services/moya-rest.service";
import {ViplistService} from "./services/viplist.service";
import {CacheService} from "./services/cache.service";
import {UserService} from "./services/user.service";
@NgModule({
......@@ -20,7 +22,9 @@ export class MoyaRestModule {
ngModule: MoyaRestModule,
providers: [
MoyaRestService,
ViplistService
ViplistService,
CacheService,
UserService
]
};
}
......
/* tslint:disable:no-unused-variable */
import { TestBed, async, inject } from '@angular/core/testing';
import { CacheService } from './cache.service';
describe('CacheService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [CacheService]
});
});
it('should ...', inject([CacheService], (service: CacheService) => {
expect(service).toBeTruthy();
}));
});
import {Injectable} from "@angular/core";
import {Observable, Subscriber, Subscription} from "rxjs";
const DEFAULT_EXPIRE_MS = 300000; // 5min
class CachedItem {
moduleName: string;
path: string;
value: any;
expires: Date;
isObservable: boolean;
constructor(moduleName: string, path: string, value: any, isObservable? : boolean) {
this.moduleName = moduleName;
this.path = path;
this.value = value;
this.expires = new Date();
this.expires.setTime(Date.now() + DEFAULT_EXPIRE_MS);
this.isObservable = isObservable;
}
public isExpired(): boolean {
return this.expires.getTime() < Date.now();
}
}
/**
* Basic cacheService.
*
* First this was part of moyaRestService, but I thinked that if cache is too easy to use, someone will kill this application with it.
* So let's make it litlebit harder
*/
@Injectable()
export class CacheService {
private timerSubscription : Subscription;
private timerObservable: Observable<any>;
private cache: Map<string, CachedItem>;
// TODO: timed clearup for cache to save memory
constructor() {
this.cache = new Map<string, CachedItem>();
this.timerObservable = Observable.timer(30000, 30000); // afther 0.5min, every 0.5min.
}
/**
* This will return observable with value from cache, or cache value from your observable and return it in observable.
*
* example: return cacheService.cacheObservable("mymodule",<url>, http.get(<url>).map(v => v.json()));
*
* @param moduleName
* @param cachePath
* @param source: {Observable<any>} Observable where the values, which are cached, is coming from
* @return {Observable<any>}: if value is in cache, this is observable made from this value. Otherwise this is source observable.
*/
public cacheObservable(moduleName: string, cachePath: string, source: Observable<any>): Observable<any> {
let cacheName = this.generateCachename(moduleName, cachePath);
if(this.cache.has(cacheName) && this.cache.get(cacheName).expires.getTime() < Date.now()) {
console.log(cacheName, " Expired version in cache");
this.cache.delete(cacheName);
}
if(!this.cache.has(cacheName)) {
/*
not in cache, let's change source to hot -observable, and return one instance from it
problem is, that before this observable is runned, there can be queries for this value from cache.
So, let's change this to hot-observable, and put that hot-observable into the cache.
And in first run, we change this hot observable to it's value.
Javascript is asynchronous, so there is no need to worry some weird things that could happen in multithreaded languages.
*/
console.log(cacheName," not in cache");
let hotSource = source
.do(val => this.cache.set(cacheName, new CachedItem(moduleName,cachePath, val)))
.publish().refCount();
this.cache.set(cacheName, new CachedItem(moduleName,cachePath, hotSource, true));
this.checkCleanTimer();
return hotSource;
}
let cacheItem = this.cache.get(cacheName);
// if value is hotObservable, return it, otherwise create observable from value
if(cacheItem.isObservable) {
console.log(cacheName, " hot observable in cache ");
return cacheItem.value;
}
console.log(cacheName, " value in cache");
this.checkCleanTimer();
return Observable.of(cacheItem.value);
}
public clean(): void {
this.cache.clear();
this.checkCleanTimer();
}
public cleanExpired(): void {
this.cache.forEach((v,k) => {
if(v.isExpired()) {
console.log(k, " Cache expired");
this.cache.delete(k);
}});
this.checkCleanTimer();
}
private checkCleanTimer() {
if(this.cache.size > 0) {
if(!this.timerSubscription) {
console.log("adding clearing timer");
this.timerSubscription = this.timerObservable.subscribe(v => {this.cleanExpired();});
}
} else {
if(this.timerSubscription) {
console.log("removing clearing timer");
this.timerSubscription.unsubscribe();
}
}
}
/**
*
* @param moduleName
* @param cachePath
* @param value
*/
private set(moduleName: string, cachePath: string, value: any): void {
let cacheName = this.generateCachename(moduleName, cachePath);
this.cache.set(cacheName, new CachedItem(moduleName,cachePath, value));
}
/**
* Get one value from cache.
*
* Now when there is this observablethingy. Make public afther rethinking this.
*
* @param moduleName
* @param cachePath
* @return {any|null} Null if nothing is in cache.
*/
private get(moduleName: string, cachePath: string): CachedItem {
let cacheName = this.generateCachename(moduleName, cachePath);
if(this.cache.has(cacheName) && this.cache.get(cacheName).expires.getTime() < Date.now()) {
this.cache.delete(cacheName);
}
if(this.cache.has(cacheName)) {
return this.cache.get(cacheName);
}
return null;
}
private generateCachename(moduleName: string, cachePath: string): string {
return moduleName + "::" + cachePath;
}
}
import { Injectable } from '@angular/core';
import {Http, Response} from "@angular/http";
import {Observable} from "rxjs";
import {Observable, Observer} from "rxjs";
@Injectable()
export class MoyaRestService {
constructor(private http: Http) { }
constructor(private http: Http) { }
post(subUrl: string, body: any, pathParameters?: Map<string, string>): Observable<Response> {
......@@ -61,7 +61,7 @@ export class MoyaRestService {
console.log("statuscode not between 200 and 299", res.status);
// next stop: handlerException
throw res; // <-- javakoodarin aivot nyrjähtivät juuri ympäri
throw res;
}
return res;
......
/* tslint:disable:no-unused-variable */
import { TestBed, async, inject } from '@angular/core/testing';
import { UserService } from './user.service';
describe('UserService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [UserService]
});
});
it('should ...', inject([UserService], (service: UserService) => {
expect(service).toBeTruthy();
}));
});
import {Injectable} from "@angular/core";
import {Observable} from "rxjs";
import {User} from "../models/user.model";
import {MoyaRestService} from "./moya-rest.service";
import {CacheService} from "./cache.service";
@Injectable()
export class UserService {
constructor(private moyaRest : MoyaRestService, private cacheService : CacheService) { }
public get(id: number): Observable<User> {
if(!id || id < 0) {
return Observable.throw("There should be userid");
}
let path = "v2/user/" + id;
return this.cacheService.cacheObservable("moya:UserService", path,
this.moyaRest.get(path)
.do(v => {console.log("getting user outside of cache", path)})
.map(res => (<User> res.json()))
);
}
}
......@@ -3,11 +3,13 @@ import {MoyaRestService} from "./moya-rest.service";
import {Observable} from "rxjs";
import {Vip} from "../models/vip.model";
import {Response} from "@angular/http";
import {User} from "../models/user.model";
import {UserService} from "./user.service";
@Injectable()
export class ViplistService {
constructor(private moyaRestService: MoyaRestService) {
constructor(private moyaRestService: MoyaRestService, private userService : UserService) {
}
......@@ -19,12 +21,12 @@ export class ViplistService {
public get(searchString?: string): Observable<Array<Vip>> {
if (!searchString) {
return this.moyaRestService.get("v2/vip/all")
.map(v => v.json());
return this.moyaRestService.get("v3/vip/all")
.switchMap(res => Observable.forkJoin(...res.json().map(apiRow => this.hostPopulator(apiRow))));
}
return this.moyaRestService.get("v2/vip/search/" + searchString)
.map(v => v.json());
return this.moyaRestService.get("v3/vip/search/" + searchString)
.switchMap(v => Observable.forkJoin(...v.json().map(x => this.hostPopulator(x))));
}
......@@ -39,39 +41,45 @@ export class ViplistService {
if (!vip.id)
throw new Error("TODO: errori, tyhmä vippi");
let res: Response = await this.moyaRestService.delete("v2/vip/" + vip.id)
let res: Response = await this.moyaRestService.delete("v3/vip/" + vip.id)
.first()
.toPromise();
return res.ok;
}
/*
public deleteProm(vip: Vip): Promise<boolean> {
if (!vip.id)
throw new Error("TODO: errori, tyhmä vippi");
return this.moyaRestService.delete("v2/vip/" + vip.id)
return this.moyaRestService.delete("v3/vip/" + vip.id)
.first()
.toPromise()
.then(r => r.ok);
}
*/
public getWithId(id: number): Observable<Vip> {
return this.moyaRestService.get("v2/vip/" + id)
return this.moyaRestService.get("v3/vip/" + id)
.map(v => v.json());
}
public create(vip: Vip): Observable<Vip> {
return this.moyaRestService.post("v2/vip/create", vip)
return this.moyaRestService.post("v3/vip/create", vip)
.map(v => v.json());
}
private hostPopulator(rawVip : any): Observable<Vip> {
return this.userService.get(rawVip.hostId)
.map((u: User) => {rawVip.host = u; return <Vip> rawVip });
}
}
......
<p>
test works!
HMR TEST
<br />
<br />
<br />
{{counter}}
<button (click)="addOne()">Kasvata</button>
</p>
import { Component, OnInit } from '@angular/core';
import {Component, OnInit, Input} from '@angular/core';
@Component({
selector: 'app-test',
......@@ -7,9 +7,16 @@ import { Component, OnInit } from '@angular/core';
})
export class TestComponent implements OnInit {
constructor() { }
@Input() counter: number;
constructor() {
this.counter = 0;
}
ngOnInit() {
}
addOne() {
this.counter++;
}
}
......@@ -13,7 +13,7 @@
<div *ngFor="let vip of vips | async" class="row contentrow">
<div class="col">Kekkonen kekkonen</div>
<div class="col">{{vip.host.firstname}}</div>
<div class="col-3"><b>{{vip.shortdescr}}</b></div>
<div class="col">{{vip.description}}</div>
<div class="col-4">
......@@ -37,7 +37,7 @@
<br /><br />
<br /><br />
<!--
<table class="table table-striped">
......@@ -74,3 +74,4 @@
</tbody>
</table>
-->
......@@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
import {ViplistService} from "../moya-rest/services/viplist.service";
import {Observable} from "rxjs";
import {Vip} from "../moya-rest/models/vip.model";
import {AsyncPipe} from "@angular/common";
@Component({
selector: 'moya-viplist',
......
export const environment = {
production: false,
hmr: true
};
export const environment = {
production: true
production: true,
hmr: false
};
......@@ -4,5 +4,6 @@
// The list of which env maps to which file can be found in `angular-cli.json`.
export const environment = {
production: false
production: false,
hmr: false
};
import {NgModuleRef, ApplicationRef} from "@angular/core";
import {createNewHosts} from "@angularclass/hmr";
/**
* Created by tuukka on 14/02/17.
*/
export const hmrBootstrap = (module: any, bootstrap: () => Promise<NgModuleRef<any>>) => {
let ngModule: NgModuleRef<any>;
module.hot.accept();
bootstrap().then(mod => ngModule = mod);
module.hot.dispose(() => {
let appRef: ApplicationRef = ngModule.injector.get(ApplicationRef);
let elements = appRef.components.map(c => c.location.nativeElement);
let makeVisible = createNewHosts(elements);
ngModule.destroy();
makeVisible();
});
};
......@@ -4,9 +4,27 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { environment } from './environments/environment';
import { AppModule } from './app/app.module';
import {hmrBootstrap} from "./hmr";
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule);
const bootstrap = () => {
return platformBrowserDynamic().bootstrapModule(AppModule);
};
if (environment.hmr) {
if (module['hot']) {
console.log("starting with hmr!");
hmrBootstrap(module, bootstrap);
} else {
console.error('HMR is not enabled for webpack-dev-server!');
console.info('Are you using the --hmr flag for ng serve?');
}
} else {
bootstrap();
}
......@@ -81,6 +81,32 @@ public class UserRestViewV2 {
}
}
@GET
@Path("/{userid}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get user", response = UserPojo.class)
public Response getEventUser(@PathParam("userid") @ApiParam("User ID") Integer userId) {
try {
if (permissionBean.hasPermission(UserPermission.VIEW_ALL) == false) {
return Response.status(Response.Status.FORBIDDEN).build();
}
// Get the user
EventUser eventUser = userBean.findByUserId(userId, false);
if (eventUser == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
// Return the EventUser
return Response.ok(pojoFactory.createUserPojo(eventUser)).build();
} catch (Exception e) {
logger.error("Finding event user failed", e);
return Response.serverError().build();
}
}
@GET
@Path("/current")
@Produces(MediaType.APPLICATION_JSON)
......
package fi.codecrew.moya.rest.v3;
import com.wordnik.swagger.annotations.Api;
import fi.codecrew.moya.beans.PermissionBeanLocal;
import fi.codecrew.moya.beans.ProductBeanLocal;
import fi.codecrew.moya.beans.UserBeanLocal;
import fi.codecrew.moya.beans.VipBeanLocal;
import fi.codecrew.moya.model.EventUser;
import fi.codecrew.moya.model.Vip;
import fi.codecrew.moya.model.VipProduct;
import fi.codecrew.moya.rest.v2.pojo.VipProductPojo;
import fi.codecrew.moya.rest.v2.pojo.VipRestPojo;
import fi.codecrew.moya.rest.v3.pojo.VipRestPojoV3;
import fi.codecrew.moya.utilities.SearchQuery;
import fi.codecrew.moya.utilities.SearchResult;
import javax.ejb.EJB;
import javax.enterprise.context.RequestScoped;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.Date;
/**
* V3 remake of vipRestStuff. EventUser -> User
*/
@RequestScoped
@Path("/v3/vip")
@Api(value = "/v3/vip", description = "Vip list operations")
@Produces({ MediaType.APPLICATION_JSON + "; charset=UTF-8" })
@Consumes({ MediaType.APPLICATION_JSON })
public class VipRestViewV3 {
@EJB
private VipBeanLocal vipbean;
@EJB
private PermissionBeanLocal permbean;
@EJB
private UserBeanLocal userbean;
@EJB
private ProductBeanLocal prodbean;
@GET
@Path("/all")
public Response getAllVips() {
ArrayList<VipRestPojoV3> ret = VipRestPojoV3.create(vipbean.getAvailableVips());
return Response.ok(ret).build();
}
@GET
@Path("/search/{searchQuery}")
public Response getAllVips(@PathParam(value = "searchQuery") String search) {
SearchQuery sq = new SearchQuery();
sq.setSearch(search);
sq.setPagesize(0);
SearchResult<Vip> result = vipbean.search(sq);
ArrayList<VipRestPojoV3> ret = VipRestPojoV3.create(result.getResults());
return Response.ok(ret).build();
}
@DELETE
@Path("/{id}")
public Response deleteEntry(@PathParam("id") Integer id) {
Vip vip = vipbean.find(id);
vipbean.delete(vip);
return Response.ok(VipRestPojoV3.create(vip)).build();
}
@GET
@Path("/{id}")
public Response findEntry(@PathParam("id") Integer id) {
Vip vip = vipbean.find(id);
return Response.ok(VipRestPojoV3.create(vip)).build();
}
@POST
@Path("/create")
@Produces({ MediaType.APPLICATION_JSON })
@Consumes(MediaType.APPLICATION_JSON)
public Response createEntry(VipRestPojoV3 create) {
Vip vip = new Vip();
EventUser curruser = permbean.getCurrentUser();
EventUser eventuser = null;
if (create.userId != null) {
eventuser = userbean.findByUserId(create.userId, false);
}
EventUser host = curruser;
if (create.hostId != null) {
host = userbean.findByUserId(create.hostId, false);
}
vip.setDescription(create.description);
vip.setShortdescr(create.shortdescr);
vip.setCreated(new Date());
vip.setEvent(curruser.getEvent());
vip.setEventUser(eventuser);
vip.setCreator(curruser);
vip.setHost(host);
for (VipProductPojo p : create.products) {
VipProduct prod = new VipProduct(vip);
vip.getProducts().add(prod);
if (p.productId != null) {
prod.setProduct(prodbean.findById(p.productId));
} else {
prod.setName(p.name);
}
prod.setQuantity(p.quantity);
prod.setNotes(p.notes);
}
vipbean.create(vip);
return Response.ok(VipRestPojoV3.create(vip)).build();
}
}
package fi.codecrew.moya.rest.v3.pojo;
import com.wordnik.swagger.annotations.ApiModel;
import fi.codecrew.moya.model.Vip;
import fi.codecrew.moya.model.VipProduct;
import fi.codecrew.moya.rest.v2.pojo.VipProductPojo;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* V3 version of vipstuff, eventuser -> user
*/
@XmlRootElement()
@ApiModel(description = "vip")
public class VipRestPojoV3 {
public Integer id;
public String description;
public String shortdescr;
public Date created;
public Integer userId;
public Integer creatorId;
public Integer hostId;
public List<VipProductPojo> products = new ArrayList<>();
public static ArrayList<VipRestPojoV3> create(List<Vip> vips) {
ArrayList<VipRestPojoV3> ret = new ArrayList<>();
for (Vip v : vips) {
ret.add(create(v));
}
return ret;
}
public static VipRestPojoV3 create(Vip v) {
VipRestPojoV3 r = new VipRestPojoV3();
r.id = v.getId();
r.description = v.getDescription();
r.shortdescr = v.getShortdescr();
r.created = v.getCreated();
if (v.getEventUser() != null) {
r.userId = v.getEventUser().getUser().getId();
}
if (v.getCreator() != null) {
r.creatorId = v.getCreator().getUser().getId();
}
if (v.getHost() != null) {
r.hostId = v.getHost().getUser().getId();
}
for (VipProduct prod : v.getProducts()) {
r.products.add(VipProductPojo.create(prod));
}
return r;
}
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!