/*
* Copyright [2014] [Christian Loehnert, krampenschiesser@gmail.com]
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.ks.activity.context;
import de.ks.activity.ActivityLoadFinishedEvent;
import de.ks.activity.executor.ActivityExecutor;
import de.ks.activity.executor.ActivityJavaFXExecutor;
import de.ks.activity.initialization.ActivityInitialization;
import de.ks.binding.Binding;
import de.ks.datasource.DataSource;
import de.ks.eventsystem.bus.EventBus;
import de.ks.validation.ValidationRegistry;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.lang.management.ManagementFactory;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
@ActivityScoped
public class ActivityStore {
static final boolean isDebugging;
static {
isDebugging = ManagementFactory.getRuntimeMXBean().getInputArguments().stream().filter(s -> s.contains("jdwp")).findFirst().isPresent();
}
static enum LoadOrSave {
LOAD, SAVE;
}
private static final Logger log = LoggerFactory.getLogger(ActivityStore.class);
@Inject
protected Binding binding;
@Inject
protected ActivityExecutor executor;
@Inject
protected ActivityJavaFXExecutor javaFXExecutor;
@Inject
protected ActivityContext context;
@Inject
protected ActivityInitialization initialization;
@Inject
protected ValidationRegistry registry;
@Inject
protected EventBus eventBus;
protected final SimpleObjectProperty<Object> model = new SimpleObjectProperty<>();
protected final SimpleBooleanProperty loading = new SimpleBooleanProperty(false);
protected final SimpleBooleanProperty stopping = new SimpleBooleanProperty(false);
protected final ConcurrentLinkedDeque<Runnable> queue = new ConcurrentLinkedDeque<>();
protected final AtomicBoolean inExecution = new AtomicBoolean();
protected DataSource datasource;
protected volatile CompletableFuture<Void> loadingFuture;
protected volatile CompletableFuture<Object> savingFuture;
@PostConstruct
public void initialize() {
model.addListener(binding::bindChangedModel);
}
@SuppressWarnings("unchecked")
public <E> E getModel() {
return (E) model.get();
}
public void setModel(Object model) {
log.info("Setting new model {}", model);
this.model.set(null);
this.model.set(model);
}
public SimpleObjectProperty<?> getModelProperty() {
return model;
}
public Binding getBinding() {
return binding;
}
public void setDatasource(DataSource<?> datasource) {
this.datasource = datasource;
}
public DataSource<?> getDatasource() {
return datasource;
}
protected boolean advanceInQueue() {
Runnable first = queue.peekFirst();
if (first != null && inExecution.compareAndSet(false, true)) {
syncSetLoadingProperty();
log.trace("Advanced in queue, next task is {}", first);
first.run();
return true;
} else {
log.trace("Could not advance in queue");
}
return false;
}
private void syncSetLoadingProperty() {
if (stopping.get()) {
return;
}
try {
javaFXExecutor.submit(() -> loading.set(true)).get();
} catch (InterruptedException e) {
//
} catch (ExecutionException e) {
log.error("Coulöd not set loading property", e);
}
}
protected synchronized void finishExecution() {
queue.removeFirst();
inExecution.set(false);
log.trace("Finished execution");
if (!advanceInQueue()) {
javaFXExecutor.submit(() -> loading.set(false));
}
}
@SuppressWarnings("unchecked")
protected void doReload() {
loadingFuture = null;
CompletableFuture<Object> load = CompletableFuture.supplyAsync(() -> {
return datasource.loadModel(m -> {
if (m != null) {
initialization.getDataStoreCallbacks().forEach(c -> {
try {
c.duringLoad(m);
} catch (ClassCastException e) {
if (c.ignoreTypeMismatch()) {
log.trace("Type mismatch in callback: ", e);
} else {
throw e;
}
}
});
}
});
}, executor);
boolean isFirstScheduling = load.getNumberOfDependents() == 0;
if (isFirstScheduling) {
loadingFuture = load.thenApplyAsync((value) -> {
log.debug("Loaded model '{}'", value);
setModel(value);
return value;
}, javaFXExecutor).thenAcceptAsync((value) -> {
try {
eventBus.post(new ActivityLoadFinishedEvent(value));
} finally {
finishExecution();
}
}, javaFXExecutor).exceptionally((t) -> {
try {
log.error("Could not load DataSource {} for activity {}", datasource, context.getCurrentActivity(), t);
return null;
} finally {
finishExecution();
}
});
}
}
protected void runFromQueue(Runnable runnable) {
CompletableFuture<Void> future = CompletableFuture.runAsync(runnable, executor);
future.thenRun(() -> finishExecution());
future.exceptionally(t -> {
log.info("Could not execute runnable {}", runnable);
finishExecution();
return null;
});
}
@SuppressWarnings("unchecked")
protected void doSave() {
savingFuture = null;
Object model = getModel();
CompletableFuture<Object> save = CompletableFuture.supplyAsync(() -> {
log.debug("Start saving model");
datasource.saveModel(model, m -> {
getBinding().applyControllerContent(m);
initialization.getDataStoreCallbacks().forEach(c -> {
try {
c.duringSave(m);
} catch (ClassCastException e) {
if (c.ignoreTypeMismatch()) {
log.trace("Type mismatch in callback: ", e);
} else {
throw e;
}
}
});
});
log.debug("Initially saved model '{}'", model);
return model;
}, executor);
boolean isFirstScheduling = save.getNumberOfDependents() == 0;
if (isFirstScheduling) {
savingFuture = save.thenApply((value) -> {
try {
log.debug("Saved model '{}'", value);
return value;
} finally {
finishExecution();
}
}).exceptionally((t) -> {
try {
log.error("Could not save model {} DataSource {} for activity {}", model, datasource, context.getCurrentActivity(), t);
return null;
} finally {
finishExecution();
}
});
}
}
public void reload() {
queue.add(() -> doReload());
advanceInQueue();
}
public void save() {
queue.add(() -> doSave());
advanceInQueue();
}
public void executeCustomRunnable(Runnable runnable) {
queue.add(() -> runFromQueue(runnable));
advanceInQueue();
}
protected void waitForLoad() {
waitForFuture(loadingFuture, "Waited too long for loading, will continue.");
}
protected void waitForSave() {
waitForFuture(savingFuture, "Waited too long for saving, will continue.");
}
protected void waitForFuture(CompletableFuture<?> future, String msg) {
if (future == null || future.isDone()) {
return;
}
if (Platform.isFxApplicationThread()) {
return;
}
if (!isDebugging) {
try {
future.get(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
//
} catch (ExecutionException e) {
throw new RuntimeException(e.getCause());
} catch (TimeoutException e) {
log.warn(msg);
}
} else {
try {
future.join();
} catch (CancellationException e) {
//ok
}
}
}
public void waitForDataSource() {
waitForLoad();
waitForSave();
while (!queue.isEmpty()) {
waitForLoad();
waitForSave();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
//
}
}
}
public boolean isLoading() {
return loading.get();
}
public ReadOnlyBooleanProperty loadingProperty() {
return loading;
}
public void stop() {
stopping.set(true);
}
}