View Javadoc
1   package de.dlr.shepard.data.timeseries.daos;
2   
3   import static de.dlr.shepard.common.util.CypherDslHelper.internalIdIs;
4   import static de.dlr.shepard.common.util.CypherDslHelper.notDeleted;
5   import static org.neo4j.cypherdsl.core.Cypher.match;
6   import static org.neo4j.cypherdsl.core.Cypher.node;
7   
8   import de.dlr.shepard.common.neo4j.daos.GenericDAO;
9   import de.dlr.shepard.common.search.query.TimeseriesInContainerQueryBuilder;
10  import de.dlr.shepard.common.util.CypherDslHelper;
11  import de.dlr.shepard.common.util.Neo4jLabels;
12  import de.dlr.shepard.data.timeseries.model.Timeseries;
13  import de.dlr.shepard.data.timeseries.model.TimeseriesContainer;
14  import de.dlr.shepard.data.timeseries.model.TimeseriesTuple;
15  import jakarta.enterprise.context.RequestScoped;
16  import java.util.Collections;
17  import java.util.Map;
18  import java.util.NoSuchElementException;
19  import java.util.Optional;
20  import java.util.stream.Stream;
21  import java.util.stream.StreamSupport;
22  import org.neo4j.cypherdsl.core.Condition;
23  import org.neo4j.cypherdsl.core.Cypher;
24  import org.neo4j.cypherdsl.core.Node;
25  import org.neo4j.cypherdsl.core.Relationship;
26  
27  @RequestScoped
28  public class TimeseriesDAO extends GenericDAO<Timeseries> {
29  
30    private static final String TIMESERIES_CONTAINER_CYPHER_SYMBOLIC_NAME = "tsc";
31  
32    @Override
33    public Class<Timeseries> getEntityType() {
34      return Timeseries.class;
35    }
36  
37    /**
38     * Run a custom Cypher query to get a Stream of Timeseries including
39     *
40     * @param query The cypher query.
41     *              It needs to return a TimeseriesContainer named "tsc", a Timeseries "ts" and a TimeseriesTuple named "tst".
42     *              The Objects are required to be node entities known to neo4j ogm.
43     * @return Stream of Timeseries with related Objects.
44     */
45    private Stream<Timeseries> queryCypherWithRelations(String query) {
46      var result = session.query(query, Map.of(), true);
47      return StreamSupport.stream(result.spliterator(), false).map(resultEntry -> {
48        Timeseries tsObj = (Timeseries) resultEntry.get("ts");
49        TimeseriesContainer tscObj = (TimeseriesContainer) resultEntry.get(TIMESERIES_CONTAINER_CYPHER_SYMBOLIC_NAME);
50        TimeseriesTuple tsTupleObj = (TimeseriesTuple) resultEntry.get("tst");
51        tsObj.setContainer(tscObj);
52        tsObj.setTimeseriesTuple(tsTupleObj);
53        return tsObj;
54      });
55    }
56  
57    public Stream<Timeseries> getAllTimeseriesInContainer(long containerId) {
58      var tsc = node(Neo4jLabels.TIMESERIES_CONTAINER).named(TIMESERIES_CONTAINER_CYPHER_SYMBOLIC_NAME);
59      var ts = node(Neo4jLabels.TIMESERIES).named("ts");
60      var tsTuple = node(Neo4jLabels.TIMESERIES_TUPLE).named("tst");
61      var isInContainer = ts.relationshipTo(tsc, Neo4jLabels.IS_IN_CONTAINER);
62      var hasTuple = ts.relationshipTo(tsTuple, Neo4jLabels.HAS_TIMESERIES_TUPLE);
63      var query = match(isInContainer, hasTuple)
64        .where(internalIdIs(tsc, containerId).and(notDeleted(ts)))
65        .returning(ts, tsc, tsTuple)
66        .build()
67        .getCypher();
68      return queryCypherWithRelations(query);
69    }
70  
71    public long getCurrentMaximumTimeseriesId() {
72      var ts = node(Neo4jLabels.TIMESERIES);
73      var query = match(ts)
74        .returning(ts.property(Neo4jLabels.TIMESERIES_ID))
75        .orderBy(ts.property(Neo4jLabels.TIMESERIES_ID).descending())
76        .limit(1)
77        .build()
78        .getCypher();
79      try {
80        return session.query(Long.class, query, Collections.emptyMap()).iterator().next();
81      } catch (NoSuchElementException e) {
82        // If no Timeseries is found we can assume a "fresh" database and the timeseries IDs can start anew.
83        return 0;
84      }
85    }
86  
87    public Optional<Timeseries> findTimeseries(long containerId, TimeseriesTuple tsTuple) {
88      var tsTupleNode = node(Neo4jLabels.TIMESERIES_TUPLE)
89        .withProperties(
90          Neo4jLabels.MEASUREMENT,
91          Cypher.literalOf(tsTuple.getMeasurement()),
92          Neo4jLabels.DEVICE,
93          Cypher.literalOf(tsTuple.getDevice()),
94          Neo4jLabels.LOCATION,
95          Cypher.literalOf(tsTuple.getLocation()),
96          Neo4jLabels.SYMBOLIC_NAME,
97          Cypher.literalOf(tsTuple.getSymbolicName()),
98          Neo4jLabels.FIELD,
99          Cypher.literalOf(tsTuple.getField())
100       )
101       .named("tst");
102     var tsc = node(Neo4jLabels.TIMESERIES_CONTAINER).named(TIMESERIES_CONTAINER_CYPHER_SYMBOLIC_NAME);
103     var ts = node(Neo4jLabels.TIMESERIES).named("ts");
104     var query = match(
105       tsTupleNode
106         .relationshipFrom(ts, Neo4jLabels.HAS_TIMESERIES_TUPLE)
107         .relationshipTo(tsc, Neo4jLabels.IS_IN_CONTAINER)
108     )
109       .where(internalIdIs(tsc, containerId).and(notDeleted(ts)))
110       .returning(tsTupleNode, tsc, ts)
111       .build()
112       .getCypher();
113     return queryCypherWithRelations(query).findFirst();
114   }
115 
116   public Optional<Timeseries> findByTimeseriesId(long timeseriesId) {
117     var ts = node(Neo4jLabels.TIMESERIES).withProperties(Neo4jLabels.TIMESERIES_ID, Cypher.literalOf(timeseriesId));
118     var related = Cypher.anyNode();
119     var rels = ts.relationshipTo(related);
120     var query = match(ts, rels, related).where(notDeleted(ts)).returning(ts, rels, related).build().getCypher();
121     return this.findByQuery(query).findFirst();
122   }
123 
124   public Stream<Timeseries> findByQuintupleInContainer(
125     long containerId,
126     String measurement,
127     String device,
128     String location,
129     String symbolicName,
130     String field
131   ) {
132     Node container = Cypher.node(Neo4jLabels.TIMESERIES_CONTAINER).named(TIMESERIES_CONTAINER_CYPHER_SYMBOLIC_NAME);
133     Node timeseries = Cypher.node(Neo4jLabels.TIMESERIES).named("ts");
134     Node timeseriesTuple = Cypher.node(Neo4jLabels.TIMESERIES_TUPLE).named("tsp");
135     Node neighbor = Cypher.anyNode().named("nb");
136     Node timeseriesDummy = Cypher.node(Neo4jLabels.TIMESERIES).named("tsd");
137     Relationship neighborhood = timeseriesDummy.relationshipBetween(neighbor).named("nh");
138     Relationship hasTuple = timeseries.relationshipTo(timeseriesTuple, Neo4jLabels.HAS_TIMESERIES_TUPLE).named("htt");
139     Relationship isInContainer = timeseries.relationshipTo(container, Neo4jLabels.IS_IN_CONTAINER).named("iic");
140     Condition wherePart = CypherDslHelper.notDeleted(timeseries).and(
141       CypherDslHelper.isNotDeletedWithInternalId(container, containerId)
142     );
143     if (measurement != null) wherePart = wherePart.and(
144       TimeseriesInContainerQueryBuilder.hasStringProperty(timeseriesTuple, Neo4jLabels.MEASUREMENT, measurement, "eq")
145     );
146     if (device != null) wherePart = wherePart.and(
147       TimeseriesInContainerQueryBuilder.hasStringProperty(timeseriesTuple, Neo4jLabels.DEVICE, device, "eq")
148     );
149     if (location != null) wherePart = wherePart.and(
150       TimeseriesInContainerQueryBuilder.hasStringProperty(timeseriesTuple, Neo4jLabels.LOCATION, location, "eq")
151     );
152     if (symbolicName != null) wherePart = wherePart.and(
153       TimeseriesInContainerQueryBuilder.hasStringProperty(
154         timeseriesTuple,
155         Neo4jLabels.SYMBOLIC_NAME,
156         symbolicName,
157         "eq"
158       )
159     );
160     if (field != null) wherePart = wherePart.and(
161       TimeseriesInContainerQueryBuilder.hasStringProperty(timeseriesTuple, Neo4jLabels.FIELD, field, "eq")
162     );
163     String query = Cypher.match(timeseries, container, timeseriesTuple, hasTuple, isInContainer)
164       .where(wherePart)
165       .optionalMatch(neighborhood)
166       .where(timeseriesDummy.internalId().eq(timeseries.internalId()))
167       .returning(timeseries, container, timeseriesTuple, neighborhood, hasTuple, isInContainer, neighbor)
168       .build()
169       .getCypher();
170     return findByQuery(query);
171   }
172 
173   public void deleteAllTimeseriesInContainer(long containerId) {
174     var ts = node(Neo4jLabels.TIMESERIES);
175     var tsc = node(Neo4jLabels.TIMESERIES_CONTAINER);
176     var query = match(ts.relationshipTo(tsc, Neo4jLabels.IS_IN_CONTAINER))
177       .where(internalIdIs(tsc, containerId))
178       .set(ts.property(Neo4jLabels.DELETED), Cypher.literalOf(true))
179       .build()
180       .getCypher();
181     session.query(query, Collections.emptyMap());
182   }
183 }