/******************************************************************************* * Copyright (c) 2019 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Semantum Oy - initial API and implementation *******************************************************************************/ package org.simantics.scl.db; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.cojen.classfile.TypeDesc; import org.simantics.Simantics; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.VirtualGraph; import org.simantics.db.WriteGraph; import org.simantics.db.common.procedure.adapter.SyncListenerAdapter; import org.simantics.db.common.procedure.adapter.TransientCacheAsyncListener; import org.simantics.db.common.request.BinaryRead; import org.simantics.db.common.request.DelayedWriteRequest; import org.simantics.db.common.request.ReadRequest; import org.simantics.db.common.request.UnaryRead; import org.simantics.db.common.request.WriteRequest; import org.simantics.db.common.request.WriteResultRequest; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.util.Layer0Utils; import org.simantics.db.layer0.variable.Variables; import org.simantics.db.request.Read; import org.simantics.db.service.ClusterControl; import org.simantics.db.service.QueryControl; import org.simantics.db.service.SerialisationSupport; import org.simantics.db.service.VirtualGraphSupport; import org.simantics.layer0.utils.triggers.IActivationManager; import org.simantics.scl.compiler.elaboration.modules.SCLValue; import org.simantics.scl.compiler.environment.specification.EnvironmentSpecification; import org.simantics.scl.compiler.errors.DoesNotExist; import org.simantics.scl.compiler.errors.Failable; import org.simantics.scl.compiler.errors.Failure; import org.simantics.scl.compiler.internal.codegen.types.JavaTypeTranslator; import org.simantics.scl.compiler.module.Module; import org.simantics.scl.compiler.module.repository.ImportFailureException; import org.simantics.scl.compiler.runtime.RuntimeEnvironment; import org.simantics.scl.compiler.runtime.RuntimeModule; import org.simantics.scl.compiler.top.ValueNotFound; import org.simantics.scl.compiler.types.TCon; import org.simantics.scl.compiler.types.Type; import org.simantics.scl.compiler.types.Types; import org.simantics.scl.compiler.types.exceptions.MatchException; import org.simantics.scl.compiler.types.util.MultiFunction; import org.simantics.scl.osgi.SCLOsgi; import org.simantics.scl.reflection.ValueNotFoundException; import org.simantics.scl.runtime.SCLContext; import org.simantics.scl.runtime.function.Function; import org.simantics.scl.runtime.function.Function1; import org.simantics.scl.runtime.reporting.SCLReportingHandler; import org.simantics.scl.runtime.tuple.Tuple; import org.simantics.scl.runtime.tuple.Tuple0; import org.simantics.utils.DataContainer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @SuppressWarnings({"rawtypes", "unchecked"}) public class SCLFunctions { private static final Logger LOGGER = LoggerFactory.getLogger(SCLFunctions.class); public static final String GRAPH = "graph"; public static T safeExec(final Function f) { try { return (T)f.apply(Tuple0.INSTANCE); } catch (Throwable t) { LOGGER.error("safeExec caught exception", t); return null; } } public static Function resolveFunction(RuntimeModule rm, String function) throws ValueNotFound { return (Function)rm.getValue(function); } private static SCLValue resolveSCLValue(RuntimeModule rm, String function) throws ValueNotFound { return rm.getModule().getValue(function); } private static RuntimeModule resolveRuntimeModule(String module) throws ValueNotFound { Failable f = SCLOsgi.MODULE_REPOSITORY.getRuntimeModule(module); if(f.didSucceed()) return f.getResult(); else if(f == DoesNotExist.INSTANCE) throw new ValueNotFound("Didn't find module " + module); else throw new ValueNotFound(((Failure)f).toString()); } private static List getEffects(SCLValue value) throws ValueNotFoundException, ValueNotFound, MatchException { Type type = value.getType(); MultiFunction mfun = Types.matchFunction(type, 1); ArrayList concreteEffects = new ArrayList<>(); mfun.effect.collectConcreteEffects(concreteEffects); return concreteEffects; } public static List getEffects(RuntimeModule rm, String function) throws ValueNotFoundException, ValueNotFound, MatchException { return getEffects(resolveSCLValue(rm, function)); } public static List getEffects(String module, String function) throws ValueNotFoundException, ValueNotFound, MatchException { return getEffects(resolveSCLValue(resolveRuntimeModule(module), function)); } private static T evaluate(Function function, Object ... args) { return (T)function.applyArray(args); } private static T evaluate(RuntimeModule rm, String function, Object ... args) throws ValueNotFound { return evaluate(resolveFunction(rm, function), args); } public static T evaluate(String module, String function, Object ... args) throws ValueNotFound { return evaluate(resolveRuntimeModule(module), function, args); } public static T evaluateDB(String module, String function, Object ... args) throws DatabaseException { try { RuntimeModule rm = resolveRuntimeModule(module); List effects = getEffects(resolveSCLValue(rm, function)); Function f = resolveFunction(rm, function); if(effects.contains(Types.WRITE_GRAPH)) { return syncWrite(f, args); } else if(effects.contains(Types.READ_GRAPH)) { return syncRead(f, args); } else { return evaluate(f, args); } } catch (ValueNotFound e) { throw new DatabaseException("SCL Value not found: " + e.name); } catch (Throwable t) { if (t instanceof DatabaseException) throw (DatabaseException) t; throw new DatabaseException(t); } } public static T evaluateGraph(String module, String function, ReadGraph graph, Object ... args) throws DatabaseException { final SCLContext context = SCLContext.getCurrent(); SCLContext.push(context); Object oldGraph = context.put(GRAPH, graph); try { return evaluateDB(module, function, args); } finally { context.put(GRAPH, oldGraph); SCLContext.pop(); } } public static void runWithGraph(ReadGraph graph, Runnable r) { final SCLContext context = SCLContext.getCurrent(); SCLContext.push(context); Object oldGraph = context.put(GRAPH, graph); try { r.run(); } finally { context.put(GRAPH, oldGraph); SCLContext.pop(); } } private static Object[] NO_ARGS = new Object[] { Tuple0.INSTANCE }; public static void asyncRead(final Function f) throws DatabaseException { asyncRead(f, NO_ARGS); } public static void asyncRead(final Function f, final Object ... args) throws DatabaseException { final SCLContext context = SCLContext.createDerivedContext(); Simantics.getSession().asyncRequest(new ReadRequest() { @Override public void run(ReadGraph graph) throws DatabaseException { SCLContext.push(context); context.put(GRAPH, graph); try { f.applyArray(args); } finally { SCLContext.pop(); } } }); } public static T syncRead(final Function f) throws DatabaseException { return syncRead(f, NO_ARGS); } public static T syncRead(final Function f, final Object ... args) throws DatabaseException { final SCLContext context = SCLContext.getCurrent(); Object graph = context.get(GRAPH); if (graph != null) { return (T)f.applyArray(args); } else { return Simantics.getSession().syncRequest(new Read() { @Override public T perform(ReadGraph graph) throws DatabaseException { SCLContext.push(context); ReadGraph oldGraph = (ReadGraph)context.put(GRAPH, graph); try { return (T)f.applyArray(args); } finally { context.put(GRAPH, oldGraph); SCLContext.pop(); } } }); } } public static void asyncWrite(final Function f) throws DatabaseException { asyncWrite(f, NO_ARGS); } public static void asyncWrite(final Function f, final Object ... args) throws DatabaseException { SCLContext context = SCLContext.createDerivedContext(); if (Simantics.peekSession() != null) { Simantics.getSession().asyncRequest(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { SCLContext.push(context); context.put(GRAPH, graph); try { f.applyArray(args); } finally { SCLContext.pop(); } } }); } else { LOGGER.warn("No session available for asynchronous write requests"); } } public static T syncWrite(final Function f) throws DatabaseException { return syncWrite(f, NO_ARGS); } public static T syncWrite(final Function f, final Object ... args) throws DatabaseException { final SCLContext context = SCLContext.getCurrent(); Object graph = context.get(GRAPH); if (graph != null && graph instanceof WriteGraph) { return (T)f.applyArray(args); } else { if (graph != null) { LOGGER.error( "SCLContext {} for current thread {} contains an existing graph object but it is not WriteGraph - Somewhere is a function that forgets to remove the graph from the context!!", context, Thread.currentThread()); } final SCLReportingHandler printer = (SCLReportingHandler)SCLContext.getCurrent().get(SCLReportingHandler.REPORTING_HANDLER); return Simantics.getSession().syncRequest(new WriteResultRequest() { @Override public T perform(WriteGraph graph) throws DatabaseException { SCLContext.push(context); SCLReportingHandler oldPrinter = (SCLReportingHandler)context.put(SCLReportingHandler.REPORTING_HANDLER, printer); ReadGraph oldGraph = (ReadGraph)context.put(GRAPH, graph); try { return (T)f.applyArray(args); } finally { context.put(GRAPH, oldGraph); context.put(SCLReportingHandler.REPORTING_HANDLER, oldPrinter); SCLContext.pop(); } } }); } } public static T delayedSyncWrite(final Function f) throws DatabaseException { final SCLContext context = SCLContext.getCurrent(); final DataContainer dc = new DataContainer(null); DelayedWriteRequest request = new DelayedWriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { final SCLContext context = SCLContext.getCurrent(); SCLContext.push(context); ReadGraph oldGraph = (ReadGraph)context.put(GRAPH, graph); try { dc.set((T)f.apply(Tuple0.INSTANCE)); } finally { context.put(GRAPH, oldGraph); SCLContext.pop(); } } }; Object graph = context.get(GRAPH); if (graph != null) { if (graph instanceof WriteGraph) { ((WriteGraph)graph).syncRequest(request); } else { throw new DatabaseException("Caller is inside a read transaction."); } } else { Simantics.getSession().syncRequest(request); } return dc.get(); } public static T virtualSyncWriteMem(WriteGraph graph, String virtualGraphId, final Function f) throws DatabaseException { final SCLContext context = SCLContext.getCurrent(); VirtualGraphSupport vgs = graph.getService(VirtualGraphSupport.class); VirtualGraph vg = vgs.getMemoryPersistent(virtualGraphId); return graph.syncRequest(new WriteResultRequest(vg) { @Override public T perform(WriteGraph graph) throws DatabaseException { SCLContext.push(context); ReadGraph oldGraph = (ReadGraph)context.put(GRAPH, graph); try { return (T)f.apply(Tuple0.INSTANCE); } finally { context.put(GRAPH, oldGraph); SCLContext.pop(); } } }); } public static T virtualSyncWriteWS(WriteGraph graph, String virtualGraphId, final Function f) throws DatabaseException { final SCLContext context = SCLContext.getCurrent(); VirtualGraphSupport vgs = graph.getService(VirtualGraphSupport.class); VirtualGraph vg = vgs.getWorkspacePersistent(virtualGraphId); return graph.syncRequest(new WriteResultRequest(vg) { @Override public T perform(WriteGraph graph) throws DatabaseException { SCLContext.push(context); ReadGraph oldGraph = (ReadGraph)context.put(GRAPH, graph); try { return (T)f.apply(Tuple0.INSTANCE); } finally { context.put(GRAPH, oldGraph); SCLContext.pop(); } } }); } public static T readValue(final String uri) throws DatabaseException { return Simantics.getSession().syncRequest(new Read() { @Override public T perform(ReadGraph graph) throws DatabaseException { return Variables.getVariable(graph, uri).getValue(graph); } }); } public static void writeValue(final String uri, final T value) throws DatabaseException { Simantics.getSession().syncRequest(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { Variables.getVariable(graph, uri).setValue(graph, value); } }); } public static void activateOnce(Resource r) { Simantics.getSession().getService(IActivationManager.class).activateOnce(r); } public static void syncActivateOnce(WriteGraph graph, Resource r) throws DatabaseException { graph.getService(IActivationManager.class).activateOnce(graph, r); } public static Resource resourceFromId(ReadGraph graph, long id) throws DatabaseException, IOException { SerialisationSupport ss = graph.getService(SerialisationSupport.class); return ss.getResource(id); } public static void disableDependencies(WriteGraph graph) { Layer0Utils.setDependenciesIndexingDisabled(graph, true); } public static void enableDependencies(WriteGraph graph) { Layer0Utils.setDependenciesIndexingDisabled(graph, false); } public static void collectClusters() { Simantics.getSession().getService(ClusterControl.class).collectClusters(Integer.MAX_VALUE); } public static class SCLUnaryRead extends BinaryRead, Object, Object> { public SCLUnaryRead(Function1 parameter1, Object parameter2) { super(parameter1, parameter2); } @Override public Object perform(ReadGraph graph) throws DatabaseException { return Simantics.applySCLRead(graph, parameter, parameter2); } } public static Object unaryQuery(ReadGraph graph, Function1 fn, Object value) throws DatabaseException { return graph.syncRequest(new SCLUnaryRead(fn, value)); } public static Object unaryQueryCached(ReadGraph graph, Function1 fn, Object value) throws DatabaseException { return graph.syncRequest(new SCLUnaryRead(fn, value), TransientCacheAsyncListener.instance()); } private static class Subquery extends UnaryRead { public Subquery(Function q) { super(q); } @Override public Object perform(ReadGraph graph) throws DatabaseException { return Simantics.applySCLRead(graph, parameter, Tuple0.INSTANCE); } } public static Object subquery(ReadGraph graph, Function q) throws DatabaseException { return graph.syncRequest(new Subquery(q)); } public static Object subqueryC(ReadGraph graph, Function q) throws DatabaseException { return graph.syncRequest(new Subquery(q), TransientCacheAsyncListener.instance()); } public static void subqueryL(ReadGraph graph, Function query, Function executeCallback, Function1 exceptionCallback, Function1 isDisposedCallback) throws DatabaseException { graph.syncRequest(new Subquery(query), new SyncListenerAdapter() { @Override public void execute(ReadGraph graph, Object result) throws DatabaseException { Simantics.applySCLRead(graph, executeCallback, result); } @Override public void exception(ReadGraph graph, Throwable t) throws DatabaseException { Simantics.applySCLRead(graph, exceptionCallback, t); } @Override public boolean isDisposed() { return isDisposedCallback.apply(Tuple0.INSTANCE); } }); } public static Object possibleFromDynamic(Type expectedType, String moduleName, Object value) { try { Failable failable = SCLOsgi.MODULE_REPOSITORY.getModule(moduleName); Module module = failable.getResult(); RuntimeEnvironment env = SCLOsgi.MODULE_REPOSITORY.createRuntimeEnvironment( EnvironmentSpecification.of(moduleName, ""), module.getParentClassLoader()); JavaTypeTranslator tr = new JavaTypeTranslator(env.getEnvironment()); TypeDesc desc = tr.toTypeDesc(expectedType); String className = desc.getFullName(); Class clazz = env.getMutableClassLoader().loadClass(className); if (!clazz.isAssignableFrom(value.getClass())) return null; } catch (ImportFailureException | ClassNotFoundException e) { } return value; } public static void restrictQueries(ReadGraph graph, int amount, int step, int maxTimeInMs) { QueryControl qc = graph.getService(QueryControl.class); long start = System.currentTimeMillis(); while(true) { int current = qc.count(); if(current < amount) return; qc.gc(graph, step); long duration = System.currentTimeMillis() - start; if(duration > maxTimeInMs) return; } } public static int countQueries(ReadGraph graph) { QueryControl qc = graph.getService(QueryControl.class); return qc.count(); } }