+package org.simantics.district.route.internal;
+
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+import org.eclipse.core.runtime.ListenerList;
+import org.simantics.Simantics;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.Session;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.request.PossibleActiveModel;
+import org.simantics.db.management.ISessionContext;
+import org.simantics.db.management.ISessionContextChangedListener;
+import org.simantics.db.management.SessionContextChangedEvent;
+import org.simantics.db.procedure.SyncListener;
+import org.simantics.district.route.Route;
+import org.simantics.district.route.RouteEvent;
+import org.simantics.district.route.RouteService;
+import org.simantics.district.route.RouteServiceListener;
+import org.simantics.district.route.Router;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class RouteServiceImpl implements RouteService, ISessionContextChangedListener, Closeable {
+
+ private final Logger LOGGER = LoggerFactory.getLogger(RouteServiceImpl.class);
+
+ private ListenerList<RouteServiceListener> listeners = new ListenerList<>();
+ private List<Route> routes = new ArrayList<>();
+ private List<Route> unmodifiableRoutes = Collections.unmodifiableList(routes);
+ private List<Router> routers = new ArrayList<>();
+ private List<Router> unmodifiableRouters = Collections.unmodifiableList(routers);
+
+ private class StoreListener implements SyncListener<Resource> {
+ private boolean disposed = false;
+
+ @Override
+ public void execute(ReadGraph graph, Resource activeModel) {
+ try {
+ if (activeModel != null) {
+ resetRoutes(RoutePersistence.findRoutes(graph, activeModel));
+ } else {
+ resetRoutes(Collections.emptyList());
+ }
+ fireEvent(RouteEvent.TYPE_ROUTE_SOURCE_CHANGED, this);
+ } catch (DatabaseException e) {
+ LOGGER.error("Failed to read routes from model {}", activeModel, e);
+ }
+ }
+
+ @Override
+ public void exception(ReadGraph graph, Throwable t) {
+ LOGGER.error("Failed to listen to current route service storage", t);
+ }
+
+ public void dispose() {
+ disposed = true;
+ }
+
+ @Override
+ public boolean isDisposed() {
+ return disposed;
+ }
+ }
+
+ private StoreListener storeListener;
+
+ private synchronized void listenToActiveModels(Session s) {
+ StoreListener sl = storeListener;
+ if (sl != null)
+ sl.dispose();
+ if (s != null) {
+ s.asyncRequest(
+ new PossibleActiveModel(Simantics.getProjectResource()),
+ storeListener = new StoreListener());
+ } else {
+ resetRoutes(Collections.emptyList());
+ }
+ }
+
+ void resetRoutes(List<RouteImpl> newRoutes) {
+ routes.clear();
+ newRoutes.forEach(r -> r.routeService(this));
+ routes.addAll(newRoutes);
+ }
+
+ @Override
+ public void sessionContextChanged(SessionContextChangedEvent event) {
+ ISessionContext ctx = event.getNewValue();
+ Session s = ctx != null ? ctx.getSession() : null;
+ listenToActiveModels(s);
+ }
+
+ public RouteServiceImpl() {
+ Session s = Simantics.peekSession();
+ Simantics.getSessionContextProvider().addContextChangedListener(this);
+ if (s != null)
+ listenToActiveModels(s);
+ }
+
+ @Override
+ public void close() {
+ Simantics.getSessionContextProvider().removeContextChangedListener(this);
+ }
+
+ @Override
+ public void addListener(RouteServiceListener l) {
+ listeners.add(l);
+ }
+
+ @Override
+ public void removeListener(RouteServiceListener l) {
+ listeners.remove(l);
+ }
+
+ @Override
+ public Route createRoute(String name, Object backendModelEntity) {
+ Route r = new RouteImpl(name).modelEntity((Resource) backendModelEntity).routeService(this);
+ fireEvent(RouteEvent.TYPE_ROUTE_CREATED, r);
+ return r;
+ }
+
+ @Override
+ public void registerRoute(Route route) {
+ routes.add(route);
+ fireEvent(RouteEvent.TYPE_ROUTE_REGISTERED, route);
+ }
+
+ @Override
+ public CompletableFuture<Route> persistRoute(Route route) {
+ fireEvent(RouteEvent.TYPE_ROUTE_PERSISTING, route);
+ CompletableFuture<Route> future = new CompletableFuture<>();
+ new RoutePersistenceJob((RouteImpl) route, RouteEvent.TYPE_ROUTE_PERSISTING, future).schedule();
+ future.thenAccept(r -> fireEvent(RouteEvent.TYPE_ROUTE_PERSISTED, r));
+ return future;
+ }
+
+ @Override
+ public CompletableFuture<Route> discardRoute(Route route) {
+ fireEvent(RouteEvent.TYPE_ROUTE_DISCARDING, route);
+ CompletableFuture<Route> result = new CompletableFuture<>();
+ RouteImpl ri = (RouteImpl) route;
+ result.thenAccept(r -> {
+ routes.remove(route);
+ fireEvent(RouteEvent.TYPE_ROUTE_DISCARDED, route);
+ });
+ if (ri.backend() != null) {
+ new RoutePersistenceJob(ri, RouteEvent.TYPE_ROUTE_DISCARDING, result).schedule();
+ } else {
+ result.complete(route);
+ }
+ return result;
+ }
+
+ @Override
+ public List<Route> listRoutes() {
+ return unmodifiableRoutes;
+ }
+
+ @Override
+ public void registerRouter(Router router) {
+ routers.add(router);
+ fireEvent(RouteEvent.TYPE_ROUTER_REGISTERED, router);
+ }
+
+ @Override
+ public void unregisterRouter(Router router) {
+ routers.add(router);
+ fireEvent(RouteEvent.TYPE_ROUTER_UNREGISTERED, router);
+ }
+
+ public List<Router> routers() {
+ return unmodifiableRouters;
+ }
+
+ void fireEvent(int type, Object obj) {
+ RouteEvent e = new RouteEvent(this, type, obj);
+ LOGGER.info("firing route event {}", e);
+ listeners.forEach(l -> {
+ try {
+ l.handleEvent(e);
+ } catch (Exception | LinkageError | AssertionError ex) {
+ LOGGER.error("Failed to invoke RouteListener {}", l, ex);
+ }
+ });
+ }
+
+}