| /******************************************************************************* |
| * Copyright (c) 2018-2019 Aston University. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * This Source Code may also be made available under the following Secondary |
| * Licenses when the conditions for such availability set forth in the Eclipse |
| * Public License, v. 2.0 are satisfied: GNU General Public License, version 3. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR GPL-3.0 |
| * |
| * Contributors: |
| * Antonio Garcia-Dominguez - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.hawk.timeaware.queries; |
| |
| import java.lang.ref.WeakReference; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Function; |
| |
| import org.eclipse.epsilon.eol.IEolModule; |
| import org.eclipse.epsilon.eol.exceptions.models.EolModelLoadingException; |
| import org.eclipse.epsilon.eol.execute.operations.contributors.OperationContributorRegistry; |
| import org.eclipse.hawk.core.IModelIndexer; |
| import org.eclipse.hawk.core.IStateListener.HawkState; |
| import org.eclipse.hawk.core.graph.IGraphEdge; |
| import org.eclipse.hawk.core.graph.IGraphIterable; |
| import org.eclipse.hawk.core.graph.IGraphNode; |
| import org.eclipse.hawk.core.graph.IGraphTransaction; |
| import org.eclipse.hawk.core.graph.timeaware.ITimeAwareGraphDatabase; |
| import org.eclipse.hawk.core.graph.timeaware.ITimeAwareGraphNode; |
| import org.eclipse.hawk.core.graph.timeaware.ITimeAwareGraphNodeIndex; |
| import org.eclipse.hawk.core.query.InvalidQueryException; |
| import org.eclipse.hawk.core.query.QueryExecutionException; |
| import org.eclipse.hawk.epsilon.emc.EOLQueryEngine; |
| import org.eclipse.hawk.epsilon.emc.contextful.AllOf; |
| import org.eclipse.hawk.epsilon.emc.contextful.TypeFirstAllOf; |
| import org.eclipse.hawk.epsilon.emc.pgetters.GraphPropertyGetter; |
| import org.eclipse.hawk.epsilon.emc.wrappers.FileNodeWrapper; |
| import org.eclipse.hawk.graph.FileNode; |
| import org.eclipse.hawk.graph.GraphWrapper; |
| import org.eclipse.hawk.graph.ModelElementNode; |
| import org.eclipse.hawk.graph.ProxyReferenceList; |
| import org.eclipse.hawk.graph.updater.GraphModelBatchInjector; |
| import org.eclipse.hawk.graph.updater.GraphModelUpdater; |
| import org.eclipse.hawk.timeaware.graph.VCSManagerIndex; |
| import org.eclipse.hawk.timeaware.graph.VCSManagerIndex.RepositoryNode; |
| import org.eclipse.hawk.timeaware.queries.operations.reflective.TimeAwareNodeHistoryOperationContributor; |
| import org.eclipse.hawk.timeaware.queries.operations.reflective.TypeHistoryOperationContributor; |
| import org.eclipse.hawk.timeaware.queries.operations.scopes.IScopingTimeAwareGraphNode; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Variant of {@link EOLQueryEngine} that exposes the time-aware nature of the |
| * index through EOL. Both types and model elements have the same set of |
| * time-aware properties and references: <code>.time</code>, |
| * <code>.earliest</code>, <code>.latest</code>, <code>.versions</code>. |
| * |
| * TODO: integrate a "human time to seconds since epoch UTC" library. |
| * |
| * TODO: allow for revision-based times as well. |
| */ |
| public class TimeAwareEOLQueryEngine extends EOLQueryEngine { |
| |
| /** |
| * Wraps a standalone node while remembering its original time, so we can |
| * retrieve the exact same node later if the weak reference has been GC'ed. |
| */ |
| public class TimeAwareGraphNodeWrapper extends GraphNodeWrapper { |
| private long time; |
| |
| protected TimeAwareGraphNodeWrapper(IGraphNode n, TimeAwareEOLQueryEngine containerModel) { |
| super(n, containerModel); |
| this.time = ((ITimeAwareGraphNode) n).getTime(); |
| } |
| |
| @Override |
| public IGraphNode getNode() { |
| IGraphNode ret = node.get(); |
| if (ret == null) { |
| TimeAwareEOLQueryEngine taQuery = (TimeAwareEOLQueryEngine)containerModel; |
| ret = taQuery.getBackend().getNodeById(id).travelInTime(time); |
| node = new WeakReference<IGraphNode>(ret); |
| } |
| return ret; |
| } |
| } |
| |
| /** |
| * Variant of {@link GraphNodeWrapper} which keeps a strong reference, in case |
| * we cannot be sure that we can simply retrieve the exact original node through |
| * ID and optionally time. This is needed for the scoping wrappers in the |
| * time-aware query DSL, as recreating those would essentially require |
| * re-running the query itself. |
| */ |
| public class StrongGraphNodeWrapper extends GraphNodeWrapper { |
| private IGraphNode node; |
| |
| protected StrongGraphNodeWrapper(IGraphNode n, EOLQueryEngine containerModel) { |
| super(n, containerModel); |
| this.node = n; |
| } |
| |
| @Override |
| public IGraphNode getNode() { |
| return node; |
| } |
| } |
| |
| /** |
| * This is the default 'all of' provider. It will go to the type node and follow |
| * all 'is of type' and/or 'is of kind' edges in reverse, with no filtering. |
| */ |
| protected class ContextlessAllOf implements AllOf { |
| public void addAllOf(IGraphNode typeNode, final String typeorkind, Collection<Object> nodes) { |
| for (IGraphEdge n : typeNode.getIncomingWithType(typeorkind)) { |
| nodes.add(new TimeAwareGraphNodeWrapper(n.getStartNode(), TimeAwareEOLQueryEngine.this)); |
| } |
| broadcastAllOfXAccess(nodes); |
| } |
| } |
| |
| /** |
| * This is the default 'all files' provider. Without a source node, it will |
| * return all available file nodes just like the regular EOL query engine. With |
| * a source node, it will return the file nodes on the same instant as that |
| * node. |
| */ |
| protected class ContextlessTimeAwareAllFiles implements Function<IGraphNode, Iterable<? extends IGraphNode>> { |
| public Iterable<? extends ITimeAwareGraphNode> apply(IGraphNode sourceNode) { |
| final ITimeAwareGraphDatabase taGraph = (ITimeAwareGraphDatabase) graph; |
| if (sourceNode == null) { |
| return taGraph.allNodes(FileNode.FILE_NODE_LABEL); |
| } else { |
| final ITimeAwareGraphNode taNode = (ITimeAwareGraphNode) sourceNode; |
| return taGraph.allNodes(FileNode.FILE_NODE_LABEL, taNode.getTime()); |
| } |
| } |
| } |
| |
| /** |
| * This is a contextful 'all files' provider, which will use glob patterns to |
| * filter the files to be returned. If there is a context node, the files to be |
| * filtered will be those that existed at the same instant. |
| */ |
| protected class GlobPatternTimeAwareAllFiles implements Function<IGraphNode, Iterable<? extends IGraphNode>> { |
| private final List<String> rplist; |
| private final List<String> fplist; |
| |
| protected GlobPatternTimeAwareAllFiles(List<String> rplist, List<String> fplist) { |
| this.rplist = rplist; |
| this.fplist = fplist; |
| } |
| |
| @Override |
| public Iterable<? extends ITimeAwareGraphNode> apply(IGraphNode sourceNode) { |
| final ITimeAwareGraphNode taNode = (ITimeAwareGraphNode) sourceNode; |
| final ITimeAwareGraphDatabase tadb = (ITimeAwareGraphDatabase) graph; |
| final GraphWrapper gw = new GraphWrapper(tadb); |
| |
| try (IGraphTransaction tx = tadb.beginTransaction()) { |
| Set<FileNode> fileNodes; |
| if (sourceNode == null) { |
| fileNodes = gw.getFileNodes(rplist, fplist); |
| } else { |
| final ITimeAwareGraphNodeIndex fileIndex = tadb.getFileIndex().travelInTime(taNode.getTime()); |
| fileNodes = gw.getFileNodes(fileIndex, rplist, fplist); |
| } |
| |
| final Set<ITimeAwareGraphNode> rawFileNodes = new HashSet<>(); |
| for (FileNode fn : fileNodes) { |
| rawFileNodes.add((ITimeAwareGraphNode) fn.getNode()); |
| } |
| tx.success(); |
| |
| return rawFileNodes; |
| } catch (Exception e) { |
| LOGGER.error("Failed to find matching files", e); |
| return Collections.emptySet(); |
| } |
| } |
| } |
| |
| private static final Logger LOGGER = LoggerFactory.getLogger(TimeAwareEOLQueryEngine.class); |
| |
| /** |
| * Provides a set of file nodes, based on some other node. {@link #getFiles()} |
| * and other similar methods rely on this, so we may use composition rather than |
| * inheritance for this type of behaviour. |
| */ |
| private Function<IGraphNode, Iterable<? extends IGraphNode>> allFiles; |
| |
| /** |
| * Provides on demand a list of instances of a type. |
| */ |
| private AllOf allOf; |
| |
| public TimeAwareEOLQueryEngine() { |
| this.allFiles = new ContextlessTimeAwareAllFiles(); |
| this.allOf = new ContextlessAllOf(); |
| } |
| |
| @Override |
| public String getType() { |
| return getClass().getCanonicalName(); |
| } |
| |
| @Override |
| protected GraphPropertyGetter createContextlessPropertyGetter() { |
| return new TimeAwareGraphPropertyGetter(graph, this); |
| } |
| |
| /** |
| * Extends "allInstances" with the concept of time. It is likely to be used |
| * through the {@link #allInstancesNow()} convenience function. |
| * |
| * The regular "allInstances" can still be used in timeline queries, where the |
| * global timepoint is changed. |
| */ |
| public Collection<?> allInstancesAt(long timepoint) { |
| final Set<Object> allContents = new HashSet<Object>(); |
| final ITimeAwareGraphDatabase taGraph = (ITimeAwareGraphDatabase) graph; |
| for (IGraphNode node : taGraph.allNodes(ModelElementNode.OBJECT_VERTEX_LABEL, timepoint)) { |
| GraphNodeWrapper wrapper = new TimeAwareGraphNodeWrapper(node, this); |
| allContents.add(wrapper); |
| } |
| return allContents; |
| } |
| |
| /** |
| * Version of {@link #allInstancesAt(long)} required to work around a limitation |
| * in Epsilon's reflective operation lookup, which does not seem to consider the |
| * promotion of integer values into long values. |
| */ |
| public Collection<?> allInstancesAt(int timepoint) { |
| return allInstancesAt((long) timepoint); |
| } |
| |
| /** |
| * Returns all the instances in the model at this current moment, by visiting |
| * the type nodes at the current point in time. |
| */ |
| public Collection<?> allInstancesNow() { |
| return allInstancesAt(System.currentTimeMillis()); |
| } |
| |
| /** |
| * Returns the proxy reference lists active at a certain point in time, within |
| * repositories whose URL matches the specified prefix. |
| * |
| * @param repositoryPrefix URL prefix to filter repositories with. |
| * @param timepoint Timepoint in milliseconds at which proxies should be listed. |
| * @throws Exception There was a problem querying the graph. |
| */ |
| public List<ProxyReferenceList> proxiesAt(String repositoryPrefix, long timepoint) throws Exception { |
| final Set<Object> visitedIDs = new HashSet<>(); |
| final List<ProxyReferenceList> proxyReferenceLists = new ArrayList<>(); |
| final ITimeAwareGraphDatabase taGraph = (ITimeAwareGraphDatabase) graph; |
| |
| try (IGraphTransaction tx = graph.beginTransaction()) { |
| ITimeAwareGraphNodeIndex idxProxies = taGraph |
| .getOrCreateNodeIndex(GraphModelBatchInjector.PROXY_DICT_NAME) |
| .travelInTime(timepoint); |
| IGraphIterable<? extends IGraphNode> proxies = idxProxies |
| .query(GraphModelUpdater.PROXY_REFERENCE_PREFIX, repositoryPrefix + "*"); |
| |
| for (IGraphNode n : proxies) { |
| if (visitedIDs.add(n.getId())) { |
| ModelElementNode men = new ModelElementNode(n); |
| proxyReferenceLists.addAll(men.getProxies()); |
| } |
| } |
| |
| tx.success(); |
| } |
| |
| return proxyReferenceLists; |
| } |
| |
| /** |
| * Convenience version of {@link #proxiesAt(String, long)} focused on the |
| * current timepoint, while allowing the user to specify a repository URL |
| * prefix. |
| */ |
| public List<ProxyReferenceList> proxiesNow(String repositoryPrefix) throws Exception { |
| return proxiesAt(repositoryPrefix, System.currentTimeMillis()); |
| } |
| |
| /** |
| * Convenience version of {@link #proxiesAt(String, long)} which goes through |
| * all repositories at the current timepoint. |
| */ |
| public List<ProxyReferenceList> proxiesNow() throws Exception { |
| return proxiesNow(""); |
| } |
| |
| /** |
| * Returns all the files indexed at a specific point in time. |
| * |
| * @param timepoint Timepoint whose files we want to list. |
| */ |
| public Set<FileNodeWrapper> getFilesAt(long timepoint) { |
| final ITimeAwareGraphDatabase taGraph = (ITimeAwareGraphDatabase) graph; |
| |
| Set<FileNodeWrapper> allFNW = new HashSet<>(); |
| for (IGraphNode n : taGraph.allNodes(FileNode.FILE_NODE_LABEL, timepoint)) { |
| allFNW.add(new FileNodeWrapper(new FileNode(n), this)); |
| } |
| return allFNW; |
| } |
| |
| /** |
| * Returns all the files indexed in the latest version of the graph. |
| */ |
| public Set<FileNodeWrapper> getFilesNow() { |
| return getFilesAt(System.currentTimeMillis()); |
| } |
| |
| @Override |
| public Object query(IModelIndexer m, String query, Map<String, Object> context) |
| throws InvalidQueryException, QueryExecutionException { |
| final HawkState currentState = m.getCompositeStateListener().getCurrentState(); |
| if (currentState != HawkState.RUNNING) { |
| throw new QueryExecutionException( |
| String.format("Cannot run the query, as the indexer is not in the RUNNING state: it is %s instead.", |
| currentState)); |
| } |
| if (!(m.getGraph() instanceof ITimeAwareGraphDatabase)) { |
| throw new QueryExecutionException(getClass().getName() + " can only be used with time-aware backends"); |
| } |
| |
| String defaultnamespaces = null; |
| if (context != null) { |
| defaultnamespaces = (String) context.get(PROPERTY_DEFAULTNAMESPACES); |
| } |
| |
| try (TimeAwareEOLQueryEngine q = new TimeAwareEOLQueryEngine()) { |
| q.load(m); |
| q.setDefaultNamespaces(defaultnamespaces); |
| if (context == null || context.isEmpty()) { |
| // nothing to do! |
| } else { |
| q.setContext(context); |
| } |
| |
| final IEolModule module = createModule(); |
| module.getContext().setOperationFactory(new TimeAwareEOLOperationFactory(q)); |
| |
| final OperationContributorRegistry opcRegistry = module.getContext().getOperationContributorRegistry(); |
| opcRegistry.add(new TimeAwareNodeHistoryOperationContributor(q)); |
| opcRegistry.add(new TypeHistoryOperationContributor(q)); |
| parseQuery(query, context, q, module); |
| return q.runQuery(module); |
| } catch (EolModelLoadingException e) { |
| throw new QueryExecutionException("Loading of EOLQueryEngine failed"); |
| } |
| } |
| |
| @Override |
| public String getHumanReadableName() { |
| return "Time Aware " + super.getHumanReadableName(); |
| } |
| |
| @Override |
| public Collection<Object> getAllOf(IGraphNode typeNode, String typeorkind) { |
| final Collection<Object> nodes = createAllOfCollection(typeNode); |
| allOf.addAllOf(typeNode, typeorkind, nodes); |
| return nodes; |
| } |
| |
| @Override |
| public Set<FileNodeWrapper> getFiles() { |
| Set<FileNodeWrapper> allFNW = new HashSet<>(); |
| for (IGraphNode rawNode : allFiles.apply(null)) { |
| allFNW.add(new FileNodeWrapper(new FileNode(rawNode), this)); |
| } |
| return allFNW; |
| } |
| |
| |
| @Override |
| public ITimeAwareGraphDatabase getBackend() { |
| return (ITimeAwareGraphDatabase) super.getBackend(); |
| } |
| |
| /** |
| * Returns the {@link RepositoryNode} associated with this instance node. |
| * |
| * @throws Exception Error while travelling in time to the matching instant on |
| * the repository node. |
| */ |
| public RepositoryNode getRepository(Object oInstanceNode) throws Exception { |
| if (oInstanceNode instanceof GraphNodeWrapper) { |
| GraphNodeWrapper gnw = (GraphNodeWrapper) oInstanceNode; |
| if (gnw.getNode() instanceof ITimeAwareGraphNode) { |
| ITimeAwareGraphNode taNode = (ITimeAwareGraphNode) gnw.getNode(); |
| ModelElementNode me = new ModelElementNode(taNode); |
| String repoURL = me.getFileNode().getRepositoryURL(); |
| |
| final ITimeAwareGraphDatabase taGraph = (ITimeAwareGraphDatabase) graph; |
| final RepositoryNode repoNode = new VCSManagerIndex(taGraph).getOrCreateRepositoryNode(repoURL); |
| |
| return repoNode.travelInTime(taNode.getTime()); |
| } |
| } |
| return null; |
| } |
| |
| private void setAllFiles(Function<IGraphNode, Iterable<? extends IGraphNode>> allFiles) { |
| this.allFiles = allFiles; |
| } |
| |
| private void setAllOf(AllOf allOf) { |
| this.allOf = allOf; |
| } |
| |
| /** |
| * Loads up the specified context into this object, changing its 'all of' and |
| * 'all files' providers. |
| */ |
| private void setContext(Map<String, Object> context) { |
| // TODO use composition over existing property getter for context restriction - |
| // needed for traversal scoping |
| |
| if (context.containsKey(PROPERTY_FILECONTEXT) || context.containsKey(PROPERTY_REPOSITORYCONTEXT)) { |
| // Set up file/repository pattern lists |
| final String sFilePatterns = (String) context.get(PROPERTY_FILECONTEXT); |
| final String[] filePatterns = (sFilePatterns != null && sFilePatterns.trim().length() != 0) |
| ? sFilePatterns.split(",") |
| : null; |
| final List<String> fplist = (filePatterns != null) ? Arrays.asList(filePatterns) : null; |
| |
| final String sRepoPatterns = (String) context.get(PROPERTY_REPOSITORYCONTEXT); |
| final String[] repoPatterns = (sRepoPatterns != null && sRepoPatterns.trim().length() != 0) |
| ? sRepoPatterns.split(",") |
| : null; |
| final List<String> rplist = (repoPatterns != null) ? Arrays.asList(repoPatterns) : null; |
| |
| /* |
| * Create suppliers for allFiles and allOf, so we may support file/repository |
| * contexts. |
| * |
| * TODO - currently limited to file first (no subtree, no derived allOf support |
| * yet). |
| */ |
| final GlobPatternTimeAwareAllFiles innerAllFiles = new GlobPatternTimeAwareAllFiles(rplist, fplist); |
| setAllFiles(innerAllFiles); |
| setAllOf(new TypeFirstAllOf(innerAllFiles, this)); |
| } |
| } |
| |
| @Override |
| public GraphNodeWrapper wrap(IGraphNode node) { |
| /* |
| * If we're wrapping a scoping node, then it has to be a strong reference-based one. |
| * Otherwise, use a weak reference-based one to save memory. |
| */ |
| if (node instanceof IScopingTimeAwareGraphNode) { |
| return new StrongGraphNodeWrapper(node, this); |
| } else { |
| return new TimeAwareGraphNodeWrapper(node, this); |
| } |
| } |
| |
| } |