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.common.procedure.adapter.DisposableListener; import org.simantics.db.common.procedure.adapter.DisposableSyncListener; 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.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 listeners = new ListenerList<>(); private List routes = new ArrayList<>(); private List unmodifiableRoutes = Collections.unmodifiableList(routes); private List routers = new ArrayList<>(); private List unmodifiableRouters = Collections.unmodifiableList(routers); private class StoreRoutesListener extends DisposableListener> { @Override public void execute(List result) { resetRoutes(result); fireEvent(RouteEvent.TYPE_ROUTE_SOURCE_CHANGED, RouteServiceImpl.this); } @Override public void exception(Throwable t) { LOGGER.error("Failed to listen to current route store routes", t); } } private class StoreListener extends DisposableSyncListener { @Override public void execute(ReadGraph graph, Resource activeModel) { if (activeModel != null) { StoreRoutesListener srl = storeRoutesListener; if (srl != null) srl.dispose(); Simantics.getSession().asyncRequest( new RoutePersistence.ModelRoutesRequest(activeModel), storeRoutesListener = new StoreRoutesListener()); } else { resetRoutes(Collections.emptyList()); fireEvent(RouteEvent.TYPE_ROUTE_SOURCE_CHANGED, RouteServiceImpl.this); } } @Override public void exception(ReadGraph graph, Throwable t) { LOGGER.error("Failed to listen to current route service storage", t); } } private StoreRoutesListener storeRoutesListener; 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 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 void refreshRoute(Route route) { fireEvent(RouteEvent.TYPE_ROUTE_MODIFIED, route); } @Override public CompletableFuture persistRoute(Route route) { fireEvent(RouteEvent.TYPE_ROUTE_PERSISTING, route); CompletableFuture 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 discardRoute(Route route) { fireEvent(RouteEvent.TYPE_ROUTE_DISCARDING, route); CompletableFuture 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 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 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); } }); } }