Bulk editor

This commit is contained in:
Patrick Haßel 2022-10-23 12:27:52 +02:00
parent 56660b2d31
commit 8f92c986aa
59 changed files with 631 additions and 296 deletions

1
.gitignore vendored
View File

@ -2,4 +2,5 @@
/target/
/.idea/
/.jpb/
/application.properties
/*.db

View File

@ -2,7 +2,7 @@ import {SearchResult} from "./SearchResult";
export interface ISearchService {
get(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void;
searchById(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void;
search(term: string, next: (results: SearchResult[]) => void, error: (error: any) => void): void;

View File

@ -0,0 +1,26 @@
import {FromJson} from "./types";
import {validateListOrEmpty, validateNumberNotNull} from "./validators";
export class Page<T> {
constructor(
readonly page: number,
readonly size: number,
readonly content: T[],
) {
// nothing
}
static fromJsonGenerator<T>(fromJson: FromJson<T>): FromJson<Page<T>> {
return (json: any) => this.fromJson2(json, fromJson);
}
static fromJson2<T>(json: any, fromJson: FromJson<T>): Page<T> {
return new Page<T>(
validateNumberNotNull(json['page']),
validateNumberNotNull(json['size']),
validateListOrEmpty(json['content'], fromJson),
);
}
}

View File

@ -0,0 +1,62 @@
import {Injectable} from '@angular/core';
import {NO_OP} from "./api.service";
import {Page} from "./Page";
import {Error, FromJson, IEntity, Next} from "./types";
import {environment} from "../../environments/environment";
import {HttpClient} from "@angular/common/http";
import {map} from "rxjs/operators";
@Injectable({
providedIn: 'root'
})
export class AbstractRepositoryService<Entity extends IEntity, Creator, Filter> {
constructor(
readonly http: HttpClient,
readonly prefix: string,
readonly fromJson: FromJson<Entity>,
) {
// nothing
}
create(dto: Creator, next: Next<Entity> = NO_OP, error: Error = NO_OP) {
this._post(['create'], dto, this.fromJson, next, error);
}
set(entity: Entity, key: string, value: any, next: Next<Entity> = NO_OP, error: Error = NO_OP) {
this._put(['put', entity.id, key], value, this.fromJson, next, error);
}
get(id: number, next: Next<Entity> = NO_OP, error: Error = NO_OP) {
this._get(['get', id], this.fromJson, next, error);
}
filter(filter: Filter, next: Next<Page<Entity>> = NO_OP, error: Error = NO_OP) {
this._post(['filter'], filter, Page.fromJsonGenerator(this.fromJson), next, error);
}
delete(id: number, next: Next<Entity> = NO_OP, error: Error = NO_OP) {
this._delete(['delete', id], this.fromJson, next, error);
}
private _get<T>(path: any[], fromJson: FromJson<T>, next: Next<T>, error: Error): void {
this.http.get<any>(this._url(path)).pipe(map(fromJson)).subscribe(next, error);
}
private _put<T>(path: any[], data: any, fromJson: FromJson<T>, next: Next<T>, error: Error): void {
this.http.put<any>(this._url(path), data).pipe(map(fromJson)).subscribe(next, error);
}
private _post<T>(path: any[], data: any, fromJson: FromJson<T>, next: Next<T>, error: Error): void {
this.http.post<any>(this._url(path), data).pipe(map(fromJson)).subscribe(next, error);
}
private _delete<T>(path: any[], fromJson: FromJson<T>, next: Next<T>, error: Error): void {
this.http.delete<any>(this._url(path)).pipe(map(fromJson)).subscribe(next, error);
}
private _url(path: any[]): string {
return environment.restBase + '/' + this.prefix + '/' + path.join('/');
}
}

View File

@ -1,16 +0,0 @@
import {TestBed} from '@angular/core/testing';
import {ApiService} from './api.service';
describe('ApiService', () => {
let service: ApiService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ApiService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -6,6 +6,7 @@ import {Subject} from "rxjs";
import {CompatClient, Stomp} from "@stomp/stompjs";
import {Update} from "./Update";
import {LocationStrategy} from "@angular/common";
import {Page} from "./Page";
export function NO_OP() {
}
@ -48,7 +49,7 @@ export class ApiService {
this.updateSubject.pipe(filter(update => update.type === type), map(update => Update.convert(update, fromJson))).subscribe(next);
}
getItem<T>(path: string, fromJson: (json: any) => T, next: (item: T) => void = NO_OP, error: (error: any) => void = NO_OP) {
getReturnItem<T>(path: string, fromJson: (json: any) => T, next: (item: T) => void = NO_OP, error: (error: any) => void = NO_OP) {
this.http.get<any>(this.restUrl(path)).pipe(map(fromJson)).subscribe(next, errorInterceptor(error));
}
@ -64,6 +65,18 @@ export class ApiService {
this.http.post<any>(this.restUrl(path), data).pipe(map(fromJson)).subscribe(next, errorInterceptor(error));
}
putReturnItem<T>(path: string, data: any, fromJson: (json: any) => T, next: (item: T) => void = NO_OP, error: (error: any) => void = NO_OP) {
this.http.put<any>(this.restUrl(path), data).pipe(map(fromJson)).subscribe(next, errorInterceptor(error));
}
postReturnPage<T>(path: string, data: any, fromJson: (json: any) => T, next: (page: Page<T>) => void = NO_OP, error: (error: any) => void = NO_OP) {
this.http.post<any>(this.restUrl(path), data).pipe(map(Page.fromJsonGenerator(fromJson))).subscribe(next, errorInterceptor(error));
}
deleteReturnItem<T>(path: string, fromJson: (json: any) => T, next: (item: T) => void = NO_OP, error: (error: any) => void = NO_OP) {
this.http.delete<any>(this.restUrl(path)).pipe(map(fromJson)).subscribe(next, errorInterceptor(error));
}
postReturnList<T>(path: string, data: any, fromJson: (json: any) => T, next: (list: T[]) => void = NO_OP, error: (error: any) => void = NO_OP) {
this.http.post<any>(this.restUrl(path), data).pipe(map(list => list.map(fromJson))).subscribe(next, errorInterceptor(error));
}

View File

@ -24,8 +24,8 @@ export class AreaService implements ISearchService {
this.api.subscribe("AreaDto", Area.fromJson, next);
}
get(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void {
this.api.getItem("area/getById/" + id, SearchResult.fromJson, next, error);
searchById(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void {
this.api.getReturnItem("area/searchById/" + id, SearchResult.fromJson, next, error);
}
search(term: string, next: (results: SearchResult[]) => void = NO_OP, error: (error: any) => void = NO_OP): void {

View File

@ -4,11 +4,11 @@ import {BulkEntry} from "./BulkEntry";
export class Bulk {
private constructor(
public id: number,
public version: number,
public name: string,
public enabled: boolean,
public entries: BulkEntry[],
readonly id: number,
readonly version: number,
readonly name: string,
readonly enabled: boolean,
readonly entries: BulkEntry[],
) {
// nothing
}

View File

@ -1,9 +1,13 @@
import {Property} from "../property/Property";
import {validateNumberNotNull} from "../validators";
import {validateBooleanNotNull, validateNumberNotNull} from "../validators";
export class BulkEntry {
private constructor(
readonly id: number,
readonly version: number,
readonly position: number,
readonly enabled: boolean,
readonly property: Property,
readonly value: number,
) {
@ -12,6 +16,10 @@ export class BulkEntry {
static fromJson(json: any): BulkEntry {
return new BulkEntry(
validateNumberNotNull(json['id']),
validateNumberNotNull(json['version']),
validateNumberNotNull(json['position']),
validateBooleanNotNull(json['enabled']),
Property.fromJson(json['property']),
validateNumberNotNull(json['value']),
);

View File

@ -4,6 +4,7 @@ import {ISearchService} from "../ISearchService";
import {SearchResult} from "../SearchResult";
import {Update} from "../Update";
import {Bulk} from "./Bulk";
import {BulkEntry} from "./BulkEntry";
@Injectable({
providedIn: 'root'
@ -24,8 +25,12 @@ export class BulkService implements ISearchService {
this.api.subscribe("BulkDto", Bulk.fromJson, next);
}
get(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void {
this.api.getItem("bulk/getById/" + id, SearchResult.fromJson, next, error);
getById(id: number, next: (results: Bulk) => void, error?: (error: any) => void): void {
this.api.getReturnItem("bulk/getById/" + id, Bulk.fromJson, next, error);
}
searchById(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void {
this.api.getReturnItem("bulk/searchById/" + id, SearchResult.fromJson, next, error);
}
search(term: string, next: (results: SearchResult[]) => void = NO_OP, error: (error: any) => void = NO_OP): void {
@ -37,11 +42,15 @@ export class BulkService implements ISearchService {
}
create(next: (item: Bulk) => void, error: (error: any) => void = NO_OP): void {
this.api.getItem("bulk/create/", Bulk.fromJson, next, error);
this.api.getReturnItem("bulk/create/", Bulk.fromJson, next, error);
}
delete(bulk: Bulk, next: () => void, error: (error: any) => void = NO_OP): void {
this.api.getItem("bulk/delete/" + bulk.id, _ => _, next, error);
this.api.getReturnItem("bulk/delete/" + bulk.id, _ => _, next, error);
}
setEntry(entry: BulkEntry, key: string, value: any, next: (result: BulkEntry) => void = NO_OP, error: (error: any) => void = NO_OP): void {
this.api.putReturnItem("BulkEntry/" + entry.id + "/set/" + key, value, next, error);
}
}

View File

@ -24,8 +24,8 @@ export class ChannelService implements ISearchService {
this.api.subscribe("ChannelDto", Channel.fromJson, next);
}
get(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void {
this.api.getItem("channel/getById/" + id, SearchResult.fromJson, next, error);
searchById(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void {
this.api.getReturnItem("channel/searchById/" + id, SearchResult.fromJson, next, error);
}
search(term: string, next: (results: SearchResult[]) => void = NO_OP, error: (error: any) => void = NO_OP): void {

View File

@ -65,7 +65,7 @@ export class DeviceService {
}
getById(id: number, next: (item: Device) => void, error: (error: any) => void = NO_OP): void {
this.api.getItem("device/getById/" + id, Device.fromJson, next, error);
this.api.getReturnItem("device/searchById/" + id, Device.fromJson, next, error);
}
create(type: string, next: (item: Device) => void, error: (error: any) => void = NO_OP): void {
@ -73,7 +73,7 @@ export class DeviceService {
}
delete(device: Device, next: () => void, error: (error: any) => void = NO_OP): void {
this.api.getItem("device/delete/" + device.id, _ => _, next, error);
this.api.getReturnItem("device/delete/" + device.id, _ => _, next, error);
}
}

View File

@ -24,8 +24,8 @@ export class PropertyService implements ISearchService {
this.api.subscribe("PropertyDto", Property.fromJson, next);
}
get(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void {
this.api.getItem("property/getById/" + id, SearchResult.fromJson, next, error);
searchById(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void {
this.api.getReturnItem("property/searchById/" + id, SearchResult.fromJson, next, error);
}
search(term: string, next: (results: SearchResult[]) => void = NO_OP, error: (error: any) => void = NO_OP): void {
@ -37,7 +37,7 @@ export class PropertyService implements ISearchService {
}
create(next: (item: Property) => void, error: (error: any) => void = NO_OP): void {
this.api.getItem("property/create/", Property.fromJson, next, error);
this.api.getReturnItem("property/create/", Property.fromJson, next, error);
}
}

View File

@ -24,8 +24,8 @@ export class RoomService implements ISearchService {
this.api.subscribe("RoomDto", Room.fromJson, next);
}
get(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void {
this.api.getItem("room/getById/" + id, SearchResult.fromJson, next, error);
searchById(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void {
this.api.getReturnItem("room/searchById/" + id, SearchResult.fromJson, next, error);
}
search(term: string, next: (results: SearchResult[]) => void = NO_OP, error: (error: any) => void = NO_OP): void {

View File

@ -24,8 +24,8 @@ export class SceneService implements ISearchService {
this.api.subscribe("SceneDto", Scene.fromJson, next);
}
get(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void {
this.api.getItem("scene/getById/" + id, SearchResult.fromJson, next, error);
searchById(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void {
this.api.getReturnItem("scene/searchById/" + id, SearchResult.fromJson, next, error);
}
search(term: string, next: (results: SearchResult[]) => void = NO_OP, error: (error: any) => void = NO_OP): void {

View File

@ -23,11 +23,11 @@ export class ScheduleEntryService {
}
create(schedule: Schedule, next: (item: ScheduleEntry) => void, error: (error: any) => void = NO_OP): void {
this.api.getItem("schedule/entry/create/" + schedule.id, ScheduleEntry.fromJson, next, error);
this.api.getReturnItem("schedule/entry/create/" + schedule.id, ScheduleEntry.fromJson, next, error);
}
delete(entry: ScheduleEntry, next: () => void, error: (error: any) => void = NO_OP): void {
this.api.getItem("schedule/entry/delete/" + entry.id, _ => _, next, error);
this.api.getReturnItem("schedule/entry/delete/" + entry.id, _ => _, next, error);
}
}

View File

@ -22,15 +22,15 @@ export class ScheduleService {
}
getById(id: number, next: (item: Schedule) => void, error: (error: any) => void = NO_OP): void {
this.api.getItem("schedule/getById/" + id, Schedule.fromJson, next, error);
this.api.getReturnItem("schedule/searchById/" + id, Schedule.fromJson, next, error);
}
create(next: (item: Schedule) => void, error: (error: any) => void = NO_OP): void {
this.api.getItem("schedule/create/", Schedule.fromJson, next, error);
this.api.getReturnItem("schedule/create/", Schedule.fromJson, next, error);
}
delete(schedule: Schedule, next: () => void, error: (error: any) => void = NO_OP): void {
this.api.getItem("schedule/delete/" + schedule.id, _ => _, next, error);
this.api.getReturnItem("schedule/delete/" + schedule.id, _ => _, next, error);
}
}

View File

@ -0,0 +1,7 @@
export type FromJson<T> = (json: any) => T;
export type Next<T> = (entity: T) => void;
export type Error = () => void
export interface IEntity {
get id(): number;
}

View File

@ -1,5 +1,5 @@
import {Component, OnInit} from '@angular/core';
import {BulkService} from "../../../api/bulk/bulkService";
import {BulkService} from "../../../api/bulk/BulkService";
import {Bulk} from "../../../api/bulk/Bulk";
import {faCheckCircle, faCircle, faTimesCircle} from "@fortawesome/free-regular-svg-icons";

View File

@ -1 +1,46 @@
<p>bulk works!</p>
<table>
<tr>
<th>Eigenschaft</th>
<th>Wert</th>
</tr>
<ng-container *ngIf="bulk">
<tr *ngFor="let entry of bulk.entries">
<td>
<app-search [searchService]="propertyService" [allowEmpty]="false" [initial]="entry.property?.id" (valueChange)="setEntry(entry, 'property', $event)"></app-search>
</td>
<ng-container [ngSwitch]="entry.property?.type">
<td *ngSwitchCase="'BOOLEAN'" [class.true]="entry.value" [class.false]="!entry.value" (click)="setEntry(entry, 'value', entry.value > 0 ? 0 : 1)">
{{entry.value ? "An" : "Aus"}}
</td>
<td *ngSwitchCase="'SHUTTER'" [class.true]="entry.value === 0" [class.false]="entry.value === 100" [class.tristate]="0 < entry.value && entry.value < 100">
<select [ngModel]="entry.value" (ngModelChange)="setEntry(entry, 'value', entry.value)">
<option [ngValue]="0">100% Offen</option>
<option [ngValue]="35">&nbsp;50%</option>
<option [ngValue]="55">&nbsp;75%</option>
<option [ngValue]="75">&nbsp;90% Sonnenschutz</option>
<option [ngValue]="85">100% Schlitze</option>
<option [ngValue]="100">100% Geschlossen</option>
</select>
</td>
<td *ngSwitchCase="'BRIGHTNESS_PERCENT'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
<select [ngModel]="entry.value" (ngModelChange)="setEntry(entry, 'value', entry.value)">
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
</select>
</td>
<td *ngSwitchCase="'COLOR_TEMPERATURE'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
<select [ngModel]="entry.value" (ngModelChange)="setEntry(entry, 'value', entry.value)">
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
</select>
</td>
<td *ngSwitchCase="'LUX'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
<select [ngModel]="entry.value" (ngModelChange)="setEntry(entry, 'value', entry.value)">
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
</select>
</td>
<td *ngSwitchDefault class="empty">
&nbsp;
</td>
</ng-container>
</tr>
</ng-container>
</table>

View File

@ -1,4 +1,9 @@
import {Component, OnInit} from '@angular/core';
import {BulkService} from "../../../api/bulk/BulkService";
import {Bulk} from "../../../api/bulk/Bulk";
import {ActivatedRoute} from "@angular/router";
import {PropertyService} from "../../../api/property/property.service";
import {BulkEntry} from "../../../api/bulk/BulkEntry";
@Component({
selector: 'app-bulk',
@ -7,10 +12,36 @@ import {Component, OnInit} from '@angular/core';
})
export class BulkComponent implements OnInit {
constructor() {
bulk!: Bulk;
constructor(
readonly activatedRoute: ActivatedRoute,
readonly bulkService: BulkService,
readonly propertyService: PropertyService,
) {
// nothing
}
ngOnInit(): void {
this.activatedRoute.paramMap.subscribe(params => {
const idString: string | null = params.get('id');
if (idString) {
this.bulkService.getById(parseInt(idString), bulk => this.bulk = bulk);
}
});
}
setEntry(entry: BulkEntry, property: string, value: any): void {
this.bulkService.setEntry(entry, property, value, result => this.update(result));
}
private update(fresh: BulkEntry): void {
const index: number = this.bulk.entries.findIndex(existing => existing.id === fresh.id);
if (index >= 0) {
this.bulk.entries[index] = fresh;
} else {
this.bulk.entries.splice(fresh.position, 0, fresh);
}
}
}

View File

@ -9,7 +9,7 @@ import {DataService} from "../../data.service";
import {PropertyService} from "../../api/property/property.service";
import {Scene} from "../../api/scene/Scene";
import {SceneService} from "../../api/scene/scene.service";
import {BulkService} from "../../api/bulk/bulkService";
import {BulkService} from "../../api/bulk/BulkService";
@Component({
selector: 'app-schedule',

View File

@ -47,7 +47,7 @@ export class SearchComponent<T> implements OnInit {
ngOnInit(): void {
if (this.initial) {
this.searchService.get(this.initial, result => this.selected = result, _ => _);
this.searchService.searchById(this.initial, result => this.selected = result, _ => _);
}
}

View File

@ -2,13 +2,12 @@ package de.ph87.homeautomation;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication
public class BackendApplication extends SpringBootServletInitializer {
public class BackendApplication {
public static void main(String[] args) {
SpringApplication.run(BackendApplication.class);
SpringApplication.run(BackendApplication.class, args);
}
}

View File

@ -1,35 +1,23 @@
package de.ph87.homeautomation;
import com.luckycatlabs.sunrisesunset.Zenith;
import de.ph87.homeautomation.bulk.Bulk;
import de.ph87.homeautomation.bulk.BulkController;
import de.ph87.homeautomation.bulk.BulkCreateDto;
import de.ph87.homeautomation.bulk.BulkEntryCreateDto;
import de.ph87.homeautomation.bulk.BulkWriter;
import de.ph87.homeautomation.bulk.BulkDto;
import de.ph87.homeautomation.bulk.entry.BulkEntryController;
import de.ph87.homeautomation.bulk.entry.BulkEntryCreateDto;
import de.ph87.homeautomation.channel.Channel;
import de.ph87.homeautomation.knx.group.KnxGroup;
import de.ph87.homeautomation.knx.group.KnxGroupReadService;
import de.ph87.homeautomation.logic.Logic;
import de.ph87.homeautomation.logic.LogicOperator;
import de.ph87.homeautomation.logic.LogicRepository;
import de.ph87.homeautomation.property.Property;
import de.ph87.homeautomation.property.PropertyRepository;
import de.ph87.homeautomation.property.PropertyType;
import de.ph87.homeautomation.scene.SceneWriteService;
import de.ph87.homeautomation.schedule.Schedule;
import de.ph87.homeautomation.schedule.ScheduleRepository;
import de.ph87.homeautomation.schedule.entry.ScheduleEntry;
import de.ph87.homeautomation.schedule.ScheduleController;
import de.ph87.homeautomation.schedule.entry.ScheduleEntryController;
import de.ph87.homeautomation.schedule.entry.ScheduleEntryType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
@SuppressWarnings({"unchecked", "UnusedReturnValue", "SameParameterValue", "UnusedAssignment", "RedundantSuppression"})
@Slf4j
@Service
@ -37,19 +25,17 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class DemoDataService {
private final PropertyRepository propertyRepository;
private final KnxGroupReadService knxGroupReadService;
private final SceneWriteService sceneWriteService;
private final LogicRepository logicRepository;
private final BulkWriter bulkWriter;
private final Config config;
private final ScheduleRepository scheduleRepository;
private final PropertyRepository propertyRepository;
private final BulkController bulkController;
private final BulkEntryController bulkEntryController;
private final ScheduleController scheduleController;
private final ScheduleEntryController scheduleEntryController;
public void insertDemoData() {
if (!config.isInsertDemoData()) {
@ -60,30 +46,13 @@ public class DemoDataService {
final Property propertyBulkShutter = createProperty("propertyBulkShutter", PropertyType.SHUTTER, null, null);
final Property propertyBulkBrightness = createProperty("propertyBulkBrightness", PropertyType.BRIGHTNESS_PERCENT, null, null);
final Property propertyBulkColorTemperature = createProperty("propertyBulkColorTemperature", PropertyType.COLOR_TEMPERATURE, null, null);
final List<BulkEntryCreateDto> entries = Arrays.asList(
new BulkEntryCreateDto(propertyBulkBoolean.getId(), 1, 0),
new BulkEntryCreateDto(propertyBulkShutter.getId(), 35, 0),
new BulkEntryCreateDto(propertyBulkBrightness.getId(), 40, 0),
new BulkEntryCreateDto(propertyBulkColorTemperature.getId(), 55, 0)
);
final Bulk bulk = createBulk("bulk", true, entries.toArray(new BulkEntryCreateDto[0]));
final Schedule schedule = createSchedule(true, "schedule");
createTime(schedule, true, 12, 0, 0, 0, propertyDirect, 1, bulk);
}
private Bulk createBulk(final String name, final boolean enabled, final BulkEntryCreateDto... entries) {
return bulkWriter.create(new BulkCreateDto(name, enabled, Arrays.stream(entries).collect(Collectors.toList())));
}
private Logic getOrCreateLogic(final String name, final LogicOperator operator, final Property... properties) {
return logicRepository.findByName(name).orElseGet(() -> {
final Logic logic = new Logic(name, operator, new HashSet<>(Arrays.asList(properties)));
return logicRepository.save(logic);
});
}
private KnxGroup knx(final int main, final int mid, final int sub) {
return knxGroupReadService.getByAddress(main, mid, sub).orElse(null);
final BulkDto bulk = bulkController.create(new BulkCreateDto("bulk", true));
bulkEntryController.create(new BulkEntryCreateDto(bulk.getId(), propertyBulkBoolean.getId(), 1, 0));
bulkEntryController.create(new BulkEntryCreateDto(bulk.getId(), propertyBulkShutter.getId(), 35, 0));
bulkEntryController.create(new BulkEntryCreateDto(bulk.getId(), propertyBulkBrightness.getId(), 40, 0));
bulkEntryController.create(new BulkEntryCreateDto(bulk.getId(), propertyBulkColorTemperature.getId(), 55, 0));
final long scheduleId = createSchedule(true, "schedule");
createTime(scheduleId, true, 12, 0, 0, 0, propertyDirect, 1, bulk);
}
private Property createProperty(final String title, final PropertyType type, final Channel readChannel, final Channel writeChannel) {
@ -93,54 +62,31 @@ public class DemoDataService {
return property;
}
private Schedule createSchedule(final boolean enabled, final String title) {
final Schedule schedule = new Schedule();
schedule.setEnabled(enabled);
schedule.setTitle(title);
return scheduleRepository.save(schedule);
private long createSchedule(final boolean enabled, final String title) {
final long id = scheduleController.create().getId();
scheduleController.setEnabled(id, enabled);
scheduleController.setTitle(id, title);
return id;
}
private ScheduleEntry createRelative(final Schedule schedule, final boolean enabled, final int inSeconds, final int fuzzySeconds, final Property property, final Object value, final Bulk bulk) {
final ZonedDateTime now = ZonedDateTime.now().plusSeconds(inSeconds).withNano(0);
return createTime(schedule, enabled, now.getHour(), now.getMinute(), now.getSecond(), fuzzySeconds, property, value, bulk);
private void createTime(final long scheduleId, final boolean enabled, final int hour, final int minute, final int second, final int fuzzySeconds, final Property property, final double value, final BulkDto bulk) {
newScheduleEntry(scheduleId, enabled, ScheduleEntryType.TIME, null, hour, minute, second, fuzzySeconds, property, value, bulk);
}
private ScheduleEntry createTime(final Schedule schedule, final boolean enabled, final int hour, final int minute, final int second, final int fuzzySeconds, final Property property, final Object value, final Bulk bulk) {
return newScheduleEntry(schedule, enabled, ScheduleEntryType.TIME, null, hour, minute, second, fuzzySeconds, property, value, bulk);
}
private ScheduleEntry createSunrise(final Schedule schedule, final boolean enabled, final Zenith zenith, final int fuzzySeconds, final Property property, final Object value, final Bulk bulk) {
return newScheduleEntry(schedule, enabled, ScheduleEntryType.SUNRISE, zenith, 0, 0, 0, fuzzySeconds, property, value, bulk);
}
private ScheduleEntry createSunset(final Schedule schedule, final boolean enabled, final Zenith zenith, final int fuzzySeconds, final Property property, final Object value, final Bulk bulk) {
return newScheduleEntry(schedule, enabled, ScheduleEntryType.SUNSET, zenith, 0, 0, 0, fuzzySeconds, property, value, bulk);
}
private ScheduleEntry newScheduleEntry(final Schedule schedule, final boolean enabled, final ScheduleEntryType type, final Zenith zenith, final int hour, final int minute, final int second, final int fuzzySeconds, final Property property, final Object value, final Bulk bulk) {
final ScheduleEntry entry = new ScheduleEntry();
entry.setEnabled(enabled);
entry.setType(type);
private void newScheduleEntry(final long scheduleId, final boolean enabled, final ScheduleEntryType type, final Zenith zenith, final int hour, final int minute, final int second, final int fuzzySeconds, final Property property, final double value, final BulkDto bulk) {
final long id = scheduleEntryController.create(scheduleId).getId();
scheduleEntryController.setEnabled(id, enabled);
scheduleEntryController.setType(id, type.name());
if (zenith != null) {
entry.setZenith(zenith.degrees().doubleValue());
scheduleEntryController.setZenith(id, zenith.degrees().doubleValue());
}
entry.setHour(hour);
entry.setMinute(minute);
entry.setSecond(second);
entry.setFuzzySeconds(fuzzySeconds);
entry.setProperty(property);
if (value instanceof Boolean) {
entry.setValue((boolean) value ? 1.0 : 0.0);
} else if (value instanceof Double) {
entry.setValue((Double) value);
} else if (value instanceof Integer) {
entry.setValue((Integer) value);
} else {
throw new RuntimeException();
}
entry.setBulk(bulk);
schedule.getEntries().add(entry);
return entry;
scheduleEntryController.setHour(id, hour);
scheduleEntryController.setMinute(id, minute);
scheduleEntryController.setSecond(id, second);
scheduleEntryController.setFuzzySeconds(id, fuzzySeconds);
scheduleEntryController.setProperty(id, property == null ? null : property.getId());
scheduleEntryController.setValue(id, value);
scheduleEntryController.setBulk(id, bulk == null ? null : bulk.getId());
}
}

View File

@ -1,5 +1,6 @@
package de.ph87.homeautomation.bulk;
import de.ph87.homeautomation.bulk.entry.BulkEntry;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@ -13,6 +14,7 @@ import java.util.List;
@Getter
@ToString
@NoArgsConstructor
@SuppressWarnings("FieldMayBeFinal")
public class Bulk {
@Id
@ -23,20 +25,24 @@ public class Bulk {
private long version;
@Setter
private boolean enabled = false;
private boolean enabled;
@Setter
@Column(nullable = false, unique = true)
private String name;
@ElementCollection
@ToString.Exclude
@OneToMany(cascade = CascadeType.ALL)
private List<BulkEntry> entries = new ArrayList<>();
public Bulk(final BulkCreateDto dto, final List<BulkEntry> entries) {
@ToString.Include
public int entryCount() {
return entries.size();
}
public Bulk(final BulkCreateDto dto) {
this.enabled = dto.isEnabled();
this.name = dto.getName();
this.entries = new ArrayList<>(entries);
}
}

View File

@ -1,7 +1,6 @@
package de.ph87.homeautomation.bulk;
import de.ph87.homeautomation.property.Property;
import de.ph87.homeautomation.property.PropertyReadService;
import de.ph87.homeautomation.property.PropertyReader;
import de.ph87.homeautomation.shared.ISearchController;
import de.ph87.homeautomation.shared.SearchResult;
import lombok.RequiredArgsConstructor;
@ -20,7 +19,7 @@ public class BulkController implements ISearchController {
private final BulkReader bulkReader;
private final PropertyReadService propertyReadService;
private final PropertyReader propertyReader;
@PostMapping("create")
public BulkDto create(@RequestBody final BulkCreateDto dto) {
@ -47,28 +46,15 @@ public class BulkController implements ISearchController {
return bulkWriter.set(id, bulk -> bulk.setEnabled(enabled));
}
@PostMapping("entryAdd/{id}")
public BulkDto entryAdd(@PathVariable final long id, @RequestBody final BulkEntryCreateDto dto) {
return bulkWriter.set(id, bulk -> {
final Property property = propertyReadService.getById(dto.getPropertyId());
bulk.getEntries().add(dto.getIndex(), new BulkEntry(dto, property));
});
}
@PostMapping("entryMove/{id}/{indexFrom}/{indexTo}")
public BulkDto entryMove(@PathVariable final long id, @PathVariable final int indexFrom, @PathVariable final int indexTo) {
return bulkWriter.set(id, bulk -> bulk.getEntries().add(indexTo, bulk.getEntries().remove(indexFrom)));
}
@PostMapping("entryRemove/{id}/{index}")
public BulkDto entryRemove(@PathVariable final long id, @PathVariable final int index) {
return bulkWriter.set(id, bulk -> bulk.getEntries().remove(index));
@GetMapping("getById/{id}")
public BulkDto getById(@PathVariable final long id) {
return bulkReader.getDtoById(id);
}
@Override
@GetMapping("getById/{id}")
@GetMapping("searchById/{id}")
@Deprecated(since = "Use 'filter' instead", forRemoval = true)
public SearchResult getById(@PathVariable final long id) {
public SearchResult searchById(@PathVariable final long id) {
return toSearchResult(bulkReader.getDtoById(id));
}

View File

@ -2,8 +2,6 @@ package de.ph87.homeautomation.bulk;
import lombok.Data;
import java.util.List;
@Data
public class BulkCreateDto {
@ -11,6 +9,4 @@ public class BulkCreateDto {
private final boolean enabled;
private final List<BulkEntryCreateDto> entries;
}

View File

@ -1,5 +1,6 @@
package de.ph87.homeautomation.bulk;
import de.ph87.homeautomation.bulk.entry.BulkEntryDto;
import lombok.Data;
import java.util.List;

View File

@ -1,14 +0,0 @@
package de.ph87.homeautomation.bulk;
import lombok.Data;
@Data
public class BulkEntryCreateDto {
private final long propertyId;
private final double value;
private final int index;
}

View File

@ -1,18 +0,0 @@
package de.ph87.homeautomation.bulk;
import de.ph87.homeautomation.property.PropertyDto;
import lombok.Data;
@Data
public class BulkEntryDto {
private final PropertyDto property;
private final double value;
public BulkEntryDto(final BulkEntry bulkEntry, final PropertyDto property) {
this.property = property;
this.value = bulkEntry.getValue();
}
}

View File

@ -5,23 +5,22 @@ import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class BulkFilter extends Filter<Bulk> {
private Long id;
private String name;
@Override
public Specification<Bulk> getSpecification() {
return (root, query, criteriaBuilder) -> {
final List<Predicate> predicates = new ArrayList<>();
if (id != null) {
predicates.add(criteriaBuilder.equal(root.get("id"), id));
}
if (name != null) {
predicates.add(criteriaBuilder.like(root.get("name"), "%" + name + "%"));
Arrays.stream(name.split("\\W+"))
.filter(p -> !p.isEmpty())
.map(name -> criteriaBuilder.like(root.get("name"), "%" + name + "%"))
.forEach(predicates::add);
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};

View File

@ -1,13 +1,15 @@
package de.ph87.homeautomation.bulk;
import de.ph87.homeautomation.bulk.entry.BulkEntry;
import de.ph87.homeautomation.bulk.entry.BulkEntryDto;
import de.ph87.homeautomation.property.PropertyDto;
import de.ph87.homeautomation.property.PropertyMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Transactional
@ -17,13 +19,11 @@ public class BulkMapper {
private final PropertyMapper propertyMapper;
public BulkDto toDto(final Bulk bulk) {
final List<BulkEntryDto> entries = bulk.getEntries().stream().map(this::toDto).collect(Collectors.toList());
return new BulkDto(bulk, entries);
final List<BulkEntryDto> entries = new ArrayList<>();
for (int position = 0; position < bulk.getEntries().size(); position++) {
entries.add(toDto(bulk.getEntries().get(position)));
}
private BulkEntryDto toDto(final BulkEntry bulkEntry) {
final PropertyDto property = propertyMapper.toDto(bulkEntry.getProperty());
return new BulkEntryDto(bulkEntry, property);
return new BulkDto(bulk, entries);
}
public BulkDto toDtoOrNull(final Bulk bulk) {
@ -33,4 +33,9 @@ public class BulkMapper {
return toDto(bulk);
}
public BulkEntryDto toDto(final BulkEntry entry) {
final PropertyDto property = propertyMapper.toDto(entry.getProperty());
return new BulkEntryDto(entry, property);
}
}

View File

@ -1,5 +1,6 @@
package de.ph87.homeautomation.bulk;
import de.ph87.homeautomation.web.NotFoundException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
@ -27,14 +28,13 @@ public class BulkReader {
return bulkRepository.findAll(filter.getSpecification(), filter.getPageable()).map(bulkMapper::toDto);
}
@Deprecated(since = "Use 'filter' instead", forRemoval = true)
public BulkDto getDtoById(final long id) {
return bulkMapper.toDto(getById(id));
}
@Deprecated(since = "Use 'filter' instead", forRemoval = true)
public List<BulkDto> findAllDtoLike(final String term) {
return bulkRepository.findAllByNameLike(term).stream().map(bulkMapper::toDto).collect(Collectors.toList());
return bulkRepository.findAllByNameLikeIgnoreCase(term).stream().map(bulkMapper::toDto).collect(Collectors.toList());
}
@Deprecated(since = "Use 'filter' instead", forRemoval = true)
@ -42,4 +42,8 @@ public class BulkReader {
return bulkRepository.findAll().stream().map(bulkMapper::toDto).collect(Collectors.toList());
}
public Bulk getBulkByEntryId(final long id) {
return bulkRepository.findByEntries_Id(id).orElseThrow(NotFoundException.supply(Bulk.class, "entries_id", id));
}
}

View File

@ -4,10 +4,13 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import java.util.List;
import java.util.Optional;
public interface BulkRepository extends JpaRepository<Bulk, Long>, JpaSpecificationExecutor<Bulk> {
@Deprecated(since = "Use 'filter' instead", forRemoval = true)
List<Bulk> findAllByNameLike(String term);
List<Bulk> findAllByNameLikeIgnoreCase(String term);
Optional<Bulk> findByEntries_Id(long id);
}

View File

@ -1,14 +1,11 @@
package de.ph87.homeautomation.bulk;
import de.ph87.homeautomation.property.PropertyReadService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@Slf4j
@Service
@ -22,11 +19,8 @@ public class BulkWriter {
private final BulkMapper bulkMapper;
private final PropertyReadService propertyReadService;
public Bulk create(final BulkCreateDto dto) {
final List<BulkEntry> entries = dto.getEntries().stream().map(e -> new BulkEntry(e, propertyReadService.getById(e.getPropertyId()))).collect(Collectors.toList());
final Bulk bulk = bulkRepository.save(new Bulk(dto, entries));
final Bulk bulk = bulkRepository.save(new Bulk(dto));
log.info("Bulk created: {}", bulk);
return bulk;
}

View File

@ -1,27 +1,42 @@
package de.ph87.homeautomation.bulk;
package de.ph87.homeautomation.bulk.entry;
import de.ph87.homeautomation.property.Property;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.ManyToOne;
import javax.persistence.*;
@Embeddable
@Entity
@Getter
@ToString
@NoArgsConstructor
public class BulkEntry {
@Id
@GeneratedValue
private long id;
@Version
private long version;
@Setter
private boolean enabled;
@Setter
private int position;
@Setter
@ManyToOne(optional = false)
private Property property;
@Setter
@Column(name = "value_")
private double value;
public BulkEntry(final BulkEntryCreateDto dto, final Property property) {
this.position = dto.getPosition();
this.property = property;
this.value = dto.getValue();
}

View File

@ -0,0 +1,44 @@
package de.ph87.homeautomation.bulk.entry;
import de.ph87.homeautomation.bulk.BulkMapper;
import de.ph87.homeautomation.property.PropertyReader;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("BulkEntry")
@RequiredArgsConstructor
public class BulkEntryController {
private final BulkEntryWriter bulkEntryWriter;
private final BulkMapper bulkMapper;
private final PropertyReader propertyReader;
@PostMapping("create")
public BulkEntryDto create(@RequestBody final BulkEntryCreateDto dto) {
return bulkMapper.toDto(bulkEntryWriter.create(dto));
}
@PutMapping("{id}/set/property")
public BulkEntryDto setValue(@PathVariable final long id, @RequestBody final long propertyId) {
return bulkMapper.toDto(bulkEntryWriter.set(id, entry -> entry.setProperty(propertyReader.getById(propertyId))));
}
@PutMapping("{id}/set/value")
public BulkEntryDto setValue(@PathVariable final long id, @RequestBody final double value) {
return bulkMapper.toDto(bulkEntryWriter.set(id, entry -> entry.setValue(value)));
}
@GetMapping("move/{id}/{indexFrom}/{indexTo}")
public BulkEntryDto move(@PathVariable final long id, @PathVariable final int indexTo) {
return bulkEntryWriter.move(id, indexTo);
}
@DeleteMapping("{id}")
public void remove(@PathVariable final long id) {
bulkEntryWriter.remove(id);
}
}

View File

@ -0,0 +1,16 @@
package de.ph87.homeautomation.bulk.entry;
import lombok.Data;
@Data
public class BulkEntryCreateDto {
private final long bulkId;
private final Long propertyId;
private final double value;
private final int position;
}

View File

@ -0,0 +1,30 @@
package de.ph87.homeautomation.bulk.entry;
import de.ph87.homeautomation.property.PropertyDto;
import lombok.Data;
@Data
public class BulkEntryDto {
private final long id;
private final long version;
private final int position;
private final boolean enabled;
private final PropertyDto property;
private final double value;
public BulkEntryDto(final BulkEntry entry, final PropertyDto propertyDto) {
this.id = entry.getId();
this.version = entry.getVersion();
this.enabled = entry.isEnabled();
this.position = entry.getPosition();
this.property = propertyDto;
this.value = entry.getValue();
}
}

View File

@ -0,0 +1,23 @@
package de.ph87.homeautomation.bulk.entry;
import de.ph87.homeautomation.property.PropertyDto;
import de.ph87.homeautomation.property.PropertyMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class BulkEntryMapper {
private final PropertyMapper propertyMapper;
public BulkEntryDto toDto(final BulkEntry entry) {
final PropertyDto property = propertyMapper.toDto(entry.getProperty());
return new BulkEntryDto(entry, property);
}
}

View File

@ -0,0 +1,77 @@
package de.ph87.homeautomation.bulk.entry;
import de.ph87.homeautomation.bulk.Bulk;
import de.ph87.homeautomation.bulk.BulkReader;
import de.ph87.homeautomation.bulk.BulkWriter;
import de.ph87.homeautomation.property.Property;
import de.ph87.homeautomation.property.PropertyReader;
import de.ph87.homeautomation.web.NotFoundException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.function.Consumer;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class BulkEntryWriter {
private final PropertyReader propertyReader;
private final BulkWriter bulkWriter;
private final BulkReader bulkReader;
private final BulkEntryMapper bulkEntryMapper;
public BulkEntry create(final BulkEntryCreateDto dto) {
final Property property = dto.getPropertyId() == null ? null : propertyReader.getById(dto.getPropertyId());
final BulkEntry entry = new BulkEntry(dto, property);
bulkWriter.set(dto.getBulkId(), bulk -> {
bulk.getEntries().add(dto.getPosition(), entry);
updatePositions(bulk);
});
log.info("BulkEntry created: {}", entry);
return entry;
}
public BulkEntryDto move(final long id, final int position) {
final Bulk bulk = bulkReader.getBulkByEntryId(id);
final BulkEntry entry = getEntryById(bulk, id);
final int oldIndex = bulk.getEntries().indexOf(entry);
bulk.getEntries().remove(entry);
bulk.getEntries().add(position, entry);
updatePositions(bulk);
log.info("BulkEntry moved from {} to {}: {}", oldIndex, position, entry);
return bulkEntryMapper.toDto(entry);
}
private static void updatePositions(final Bulk bulk) {
for (int p = 0; p < bulk.getEntries().size(); p++) {
bulk.getEntries().get(p).setPosition(p);
}
}
public void remove(final long id) {
getEntryById(id);
}
private BulkEntry getEntryById(final long id) {
final Bulk bulk = bulkReader.getBulkByEntryId(id);
return getEntryById(bulk, id);
}
private static BulkEntry getEntryById(final Bulk bulk, final long id) {
return bulk.getEntries().stream().filter(e -> e.getId() == id).findFirst().orElseThrow(NotFoundException.supply(BulkEntry.class, "Bulk#" + bulk.getId() + ".entries.id", id));
}
public BulkEntry set(final long id, final Consumer<BulkEntry> consumer) {
final BulkEntry entry = getEntryById(id);
consumer.accept(entry);
return entry;
}
}

View File

@ -22,8 +22,8 @@ public class ChannelController implements ISearchController {
}
@Override
@GetMapping("getById/{id}")
public SearchResult getById(@PathVariable final long id) {
@GetMapping("searchById/{id}")
public SearchResult searchById(@PathVariable final long id) {
return channelService.findDtoById(id).map(this::toSearchResult).orElseThrow(() -> new NotFoundException("Channel.id=" + id));
}

View File

@ -53,7 +53,7 @@ public class ChannelService {
}
public List<? extends ChannelDto> findAllDtoLike(final String term) {
return channelOwners.stream().map(owner -> owner.findAllDtoLike(term)).reduce(new ArrayList<>(), Helpers::merge);
return channelOwners.stream().map(owner -> owner.findAllDtoLikeIgnoreCase(term)).reduce(new ArrayList<>(), Helpers::merge);
}
public Optional<? extends ChannelDto> findDtoById(final long id) {

View File

@ -12,6 +12,6 @@ public interface IChannelOwner {
List<? extends ChannelDto> findAllDto();
List<? extends ChannelDto> findAllDtoLike(final String like);
List<? extends ChannelDto> findAllDtoLikeIgnoreCase(final String like);
}

View File

@ -2,7 +2,7 @@ package de.ph87.homeautomation.device;
import de.ph87.homeautomation.device.devices.Device;
import de.ph87.homeautomation.device.devices.DeviceDto;
import de.ph87.homeautomation.property.PropertyReadService;
import de.ph87.homeautomation.property.PropertyReader;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@ -19,14 +19,14 @@ public class DeviceController {
private final DeviceWriteService deviceWriteService;
private final PropertyReadService propertyReadService;
private final PropertyReader propertyReader;
@GetMapping("findAll")
public List<DeviceDto> findAll() {
return deviceReadService.findAll();
}
@GetMapping("getById/{id}")
@GetMapping("searchById/{id}")
public DeviceDto getById(@PathVariable final long id) {
return deviceReadService.getDtoById(id);
}
@ -53,22 +53,22 @@ public class DeviceController {
@PostMapping("set/{id}/DeviceSwitch/stateProperty")
public DeviceDto setDeviceSwitchStateProperty(@PathVariable final long id, @RequestBody(required = false) final Long propertyId) {
return deviceWriteService.setDeviceSwitch(id, (device, v) -> device.setStateProperty(mapOrNull(v, propertyReadService::getById)), propertyId);
return deviceWriteService.setDeviceSwitch(id, (device, v) -> device.setStateProperty(mapOrNull(v, propertyReader::getById)), propertyId);
}
@PostMapping("set/{id}/DeviceStateScene/stateProperty")
public DeviceDto setDeviceStateSceneStateProperty(@PathVariable final long id, @RequestBody(required = false) final Long propertyId) {
return deviceWriteService.setDeviceStateScene(id, (device, v) -> device.setStateProperty(mapOrNull(v, propertyReadService::getById)), propertyId);
return deviceWriteService.setDeviceStateScene(id, (device, v) -> device.setStateProperty(mapOrNull(v, propertyReader::getById)), propertyId);
}
@PostMapping("set/{id}/DeviceStateScene/sceneProperty")
public DeviceDto setDeviceStateSceneSceneProperty(@PathVariable final long id, @RequestBody(required = false) final Long propertyId) {
return deviceWriteService.setDeviceStateScene(id, (device, v) -> device.setSceneProperty(mapOrNull(v, propertyReadService::getById)), propertyId);
return deviceWriteService.setDeviceStateScene(id, (device, v) -> device.setSceneProperty(mapOrNull(v, propertyReader::getById)), propertyId);
}
@PostMapping("set/{id}/DeviceShutter/positionProperty")
public DeviceDto setDeviceShutterPositionProperty(@PathVariable final long id, @RequestBody(required = false) final Long propertyId) {
return deviceWriteService.setDeviceShutter(id, (device, v) -> device.setPositionProperty(mapOrNull(v, propertyReadService::getById)), propertyId);
return deviceWriteService.setDeviceShutter(id, (device, v) -> device.setPositionProperty(mapOrNull(v, propertyReader::getById)), propertyId);
}
}

View File

@ -41,7 +41,7 @@ public class KnxGroupChannelOwnerService implements IChannelOwner {
}
@Override
public List<KnxGroupDto> findAllDtoLike(final String like) {
public List<KnxGroupDto> findAllDtoLikeIgnoreCase(final String like) {
return knxGroupReadService.findAllLike(like).stream().map(this::toDto).collect(Collectors.toList());
}

View File

@ -19,17 +19,41 @@ import java.util.regex.Pattern;
@RequiredArgsConstructor
public class KnxGroupImportService {
private static final File ETS_HOME = new File("./data/Zuhause-ETS5");
private final KnxGroupRepository knxGroupRepository;
public void importGroups() {
try {
execute("/usr/bin/git", "fetch", "--all");
execute("/usr/bin/git", "checkout", "master", "--force");
execute("/usr/bin/git", "reset", "--hard", "origin/master");
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
knxGroupRepository.findAll().forEach(knxGroup -> knxGroup.setEts(false));
try {
Jsoup.parse(new File("./data/Zuhause-ETS5/G"), "UTF-8").select("GA").forEach(this::importGroup);
Jsoup.parse(new File(ETS_HOME, "G"), "UTF-8").select("GA").forEach(this::importGroup);
} catch (IOException e) {
log.error("Failed to import KnxGroups: {}", e.toString());
}
}
private static void execute(final String... command) throws IOException, InterruptedException {
log.info("{}", (Object) command);
final ProcessBuilder fetchBuilder = new ProcessBuilder(command);
fetchBuilder.directory(ETS_HOME);
fetchBuilder.redirectErrorStream(true);
final Process fetch = fetchBuilder.start();
fetch.waitFor();
final String output = new String(fetch.getInputStream().readAllBytes());
if (!output.matches("^\\s*$")) {
for (final String line : output.split("[\n\r]+")) {
log.info("GIT: {}", line);
}
}
}
private void importGroup(final Element ga) {
final GroupAddress address = new GroupAddress(Integer.parseInt(ga.attr("Address")));
final String name = ga.attr("Name");
@ -47,12 +71,18 @@ public class KnxGroupImportService {
}
private void setDpt(final KnxGroup knxGroup, final String dptString) {
final Matcher matcher = Pattern.compile("^DPST-(?<main>\\d+)-(?<sub>\\d+)$").matcher(dptString);
if (!matcher.matches()) {
throw new RuntimeException();
final Matcher mainSub = Pattern.compile("^DPST-(?<main>\\d+)-(?<sub>\\d+)$").matcher(dptString);
if (mainSub.matches()) {
knxGroup.setDptMain(Integer.parseInt(mainSub.group("main")));
knxGroup.setDptSub(Integer.parseInt(mainSub.group("sub")));
return;
}
final Matcher main = Pattern.compile("^DPT-(?<main>\\d+)$").matcher(dptString);
if (main.matches()) {
knxGroup.setDptMain(Integer.parseInt(main.group("main")));
knxGroup.setDptSub(0);
}
knxGroup.setDptMain(Integer.parseInt(matcher.group("main")));
knxGroup.setDptSub(Integer.parseInt(matcher.group("sub")));
}
}

View File

@ -40,7 +40,7 @@ public class LogicChannelOwner implements IChannelOwner {
}
@Override
public List<LogicDto> findAllDtoLike(final String like) {
public List<LogicDto> findAllDtoLikeIgnoreCase(final String like) {
return logicReader.findAllDtoLike(like);
}

View File

@ -30,7 +30,6 @@ public class LogicWriter {
private final WebSocketService webSocketService;
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
// @Transactional(propagation = Propagation.REQUIRES_NEW)
public void onPropertyChanged(final PropertyDto dto) {
log.debug("Listen [{}]: {}", getCurrentTransactionName(), dto.getTitle());
logicReader.findAllByPropertyId(dto.getId()).forEach(this::update);

View File

@ -16,7 +16,7 @@ public class PropertyController implements ISearchController {
private final PropertyWriteService propertyWriteService;
private final PropertyReadService propertyReadService;
private final PropertyReader propertyReader;
private final ChannelService channelService;
@ -27,7 +27,7 @@ public class PropertyController implements ISearchController {
@GetMapping("findAll")
public List<PropertyDto> findAll() {
return propertyReadService.findAllDto();
return propertyReader.findAllDto();
}
@PostMapping("set/{id}/type")
@ -47,12 +47,17 @@ public class PropertyController implements ISearchController {
@PostMapping("set/{id}/readChannel")
public PropertyDto setReadChannel(@PathVariable final long id, @RequestBody(required = false) final Long channelId) {
return propertyWriteService.set(id, (p, v) -> p.setReadChannel(channelService.getById(v)), channelId);
return propertyWriteService.set(id, (p, v) -> p.setReadChannel(channelId == null ? null : channelService.getById(v)), channelId);
}
@PostMapping("set/{id}/writeChannel")
public PropertyDto setWriteChannel(@PathVariable final long id, @RequestBody(required = false) final Long channelId) {
return propertyWriteService.set(id, (p, v) -> p.setWriteChannel(channelService.getById(v)), channelId);
return propertyWriteService.set(id, (p, v) -> p.setWriteChannel(channelId == null ? null : channelService.getById(v)), channelId);
}
@DeleteMapping("{id}")
public void delete(@PathVariable final long id) {
propertyWriteService.delete(id);
}
@PostMapping("toggle/{id}")
@ -62,7 +67,7 @@ public class PropertyController implements ISearchController {
}
private boolean getOldStateBoolean(final long id, final boolean orElse) {
final Double oldValue = propertyReadService.getDtoById(id).getValue();
final Double oldValue = propertyReader.getDtoById(id).getValue();
if (oldValue == null || oldValue.isNaN()) {
return orElse;
}
@ -70,15 +75,15 @@ public class PropertyController implements ISearchController {
}
@Override
@GetMapping("getById/{id}")
public SearchResult getById(@PathVariable final long id) {
return toSearchResult(propertyReadService.getDtoById(id));
@GetMapping("searchById/{id}")
public SearchResult searchById(@PathVariable final long id) {
return toSearchResult(propertyReader.getDtoById(id));
}
@Override
@PostMapping("searchLike")
public List<SearchResult> searchLike(@RequestBody final String term) {
return propertyReadService.findAllDtoLike("%" + term + "%").stream().map(this::toSearchResult).collect(Collectors.toList());
return propertyReader.findAllDtoLike("%" + term + "%").stream().map(this::toSearchResult).collect(Collectors.toList());
}
private SearchResult toSearchResult(final PropertyDto propertyDto) {

View File

@ -12,7 +12,7 @@ import java.util.stream.Collectors;
@Service
@Transactional
@RequiredArgsConstructor
public class PropertyReadService {
public class PropertyReader {
private final PropertyRepository propertyRepository;
@ -23,7 +23,7 @@ public class PropertyReadService {
}
public List<PropertyDto> findAllDtoLike(final String like) {
return propertyRepository.findAllByTitleLike(like).stream().map(propertyMapper::toDto).collect(Collectors.toList());
return propertyRepository.findAllByTitleLikeIgnoreCase(like).stream().map(propertyMapper::toDto).collect(Collectors.toList());
}
public PropertyDto getDtoById(final long id) {

View File

@ -1,19 +1,17 @@
package de.ph87.homeautomation.property;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
public interface PropertyRepository extends CrudRepository<Property, Long> {
public interface PropertyRepository extends JpaRepository<Property, Long> {
Optional<Property> findByTitle(String title);
List<Property> findAllByReadChannel_Id(long readChannelId);
List<Property> findAll();
List<Property> findAllByTitleLike(final String like);
List<Property> findAllByTitleLikeIgnoreCase(final String like);
boolean existsByTitle(String title);

View File

@ -26,7 +26,7 @@ public class PropertyWriteService {
private static final String TITLE_PREFIX = "NEU ";
private final PropertyReadService propertyReadService;
private final PropertyReader propertyReader;
private final ChannelService channelService;
@ -39,7 +39,7 @@ public class PropertyWriteService {
private final ApplicationEventPublisher applicationEventPublisher;
public void updateAllProperties() {
propertyReadService.findAllByReadChannelNotNull().forEach(property -> {
propertyReader.findAllByReadChannelNotNull().forEach(property -> {
final Optional<IChannelOwner> ownerOptional = channelService.findByChannel(property.getReadChannel());
if (ownerOptional.isPresent()) {
ownerOptional.get().requestUpdate(property.getReadChannel());
@ -50,10 +50,9 @@ public class PropertyWriteService {
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
// @Transactional(propagation = Propagation.REQUIRES_NEW)
public void onChannelChanged(final ChannelDto dto) {
log.debug("onChannelChanged [{}]: {}", getCurrentTransactionName(), dto.getTitle());
final List<Property> properties = propertyReadService.findAllByReadChannel_Id(dto.getId());
final List<Property> properties = propertyReader.findAllByReadChannel_Id(dto.getId());
if (!properties.isEmpty()) {
properties.forEach(property -> {
property.setValue(property.getReadChannel().getValue());
@ -86,13 +85,13 @@ public class PropertyWriteService {
}
public <T> PropertyDto set(final long id, final BiConsumer<Property, T> setter, final T value) {
final Property property = propertyReadService.getById(id);
final Property property = propertyReader.getById(id);
setter.accept(property, value);
return publish(property, true);
}
public void delete(final long id) {
final Property property = propertyReadService.getById(id);
final Property property = propertyReader.getById(id);
propertyRepository.delete(property);
publish(property, false);
}

View File

@ -19,7 +19,7 @@ public class SceneController {
return sceneReadService.findAll();
}
@GetMapping("getById/{id}")
@GetMapping("searchById/{id}")
public SceneDto getById(@PathVariable final long id) {
return sceneReadService.getDtoById(id);
}

View File

@ -1,6 +1,6 @@
package de.ph87.homeautomation.schedule;
import de.ph87.homeautomation.property.PropertyReadService;
import de.ph87.homeautomation.property.PropertyReader;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@ -15,14 +15,14 @@ public class ScheduleController {
private final ScheduleWriteService scheduleWriteService;
private final PropertyReadService propertyReadService;
private final PropertyReader propertyReader;
@GetMapping("findAll")
public List<ScheduleDto> findAll() {
return scheduleReadService.findAllDtos();
}
@GetMapping("getById/{id}")
@GetMapping("searchById/{id}")
public ScheduleDto getById(@PathVariable final long id) {
return scheduleReadService.getDtoById(id);
}

View File

@ -1,7 +1,7 @@
package de.ph87.homeautomation.schedule.entry;
import de.ph87.homeautomation.bulk.BulkReader;
import de.ph87.homeautomation.property.PropertyReadService;
import de.ph87.homeautomation.property.PropertyReader;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@ -14,7 +14,7 @@ public class ScheduleEntryController {
private final BulkReader bulkReader;
private final PropertyReadService propertyReadService;
private final PropertyReader propertyReader;
@GetMapping("create/{scheduleId}")
public ScheduleEntryDto create(@PathVariable final long scheduleId) {
@ -72,8 +72,8 @@ public class ScheduleEntryController {
}
@PostMapping("set/{id}/zenith")
public ScheduleEntryDto setZenith(@PathVariable final long id, @RequestBody final String value) {
return scheduleEntryWriteService.set(id, ScheduleEntry::setZenith, Double.parseDouble(value));
public ScheduleEntryDto setZenith(@PathVariable final long id, @RequestBody final double value) {
return scheduleEntryWriteService.set(id, ScheduleEntry::setZenith, value);
}
@PostMapping("set/{id}/hour")
@ -97,8 +97,8 @@ public class ScheduleEntryController {
}
@PostMapping("set/{id}/property")
public ScheduleEntryDto property(@PathVariable final long id, @RequestBody(required = false) final Long propertyId) {
return scheduleEntryWriteService.set(id, entry -> entry.setProperty(propertyId == null ? null : propertyReadService.getById(propertyId)));
public ScheduleEntryDto setProperty(@PathVariable final long id, @RequestBody(required = false) final Long propertyId) {
return scheduleEntryWriteService.set(id, entry -> entry.setProperty(propertyId == null ? null : propertyReader.getById(propertyId)));
}
@PostMapping("set/{id}/value")
@ -107,7 +107,7 @@ public class ScheduleEntryController {
}
@PostMapping("set/{id}/bulk")
public ScheduleEntryDto bulk(@PathVariable final long id, @RequestBody(required = false) final Long bulkId) {
public ScheduleEntryDto setBulk(@PathVariable final long id, @RequestBody(required = false) final Long bulkId) {
return scheduleEntryWriteService.set(id, entry -> entry.setBulk(bulkId == null ? null : bulkReader.getById(bulkId)));
}

View File

@ -4,7 +4,7 @@ import java.util.List;
public interface ISearchController {
SearchResult getById(final long id);
SearchResult searchById(final long id);
List<SearchResult> searchLike(final String term);

View File

@ -3,14 +3,20 @@ package de.ph87.homeautomation.web;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
import java.util.function.Supplier;
public class NotFoundException extends ResponseStatusException {
public NotFoundException(final String message) {
super(HttpStatus.NOT_FOUND, message);
public NotFoundException(final String format, final Object... args) {
super(HttpStatus.NOT_FOUND, String.format(format, args));
}
public NotFoundException(final String format, final Object... args) {
this(String.format(format, args));
public NotFoundException(final Class<?> clazz, final String key, final Object value) {
this("Entity not found: %s.%s = %s", clazz.getSimpleName(), key, value == null ? null : "\"" + value + "\"");
}
public static Supplier<NotFoundException> supply(final Class<?> clazz, final String key, final Object value) {
return () -> new NotFoundException(clazz, key, value);
}
}