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 de.dlr.shepard.common.util.Neo4jLabels.HAS_TIMESERIES_TUPLE;
6   import static de.dlr.shepard.common.util.Neo4jLabels.IS_IN_CONTAINER;
7   import static de.dlr.shepard.common.util.Neo4jLabels.TIMESERIES;
8   import static de.dlr.shepard.common.util.Neo4jLabels.TIMESERIES_CONTAINER;
9   import static de.dlr.shepard.common.util.Neo4jLabels.TIMESERIES_TUPLE;
10  import static org.neo4j.cypherdsl.core.Cypher.match;
11  import static org.neo4j.cypherdsl.core.Cypher.node;
12  
13  import de.dlr.shepard.common.neo4j.daos.GenericDAO;
14  import de.dlr.shepard.data.timeseries.model.Timeseries;
15  import de.dlr.shepard.data.timeseries.model.TimeseriesContainer;
16  import de.dlr.shepard.data.timeseries.model.TimeseriesTuple;
17  import jakarta.enterprise.context.RequestScoped;
18  import java.util.Collections;
19  import java.util.Map;
20  import java.util.NoSuchElementException;
21  import java.util.Optional;
22  import java.util.stream.Stream;
23  import java.util.stream.StreamSupport;
24  import org.neo4j.cypherdsl.core.Cypher;
25  
26  @RequestScoped
27  public class TimeseriesDAO extends GenericDAO<Timeseries> {
28  
29    @Override
30    public Class<Timeseries> getEntityType() {
31      return Timeseries.class;
32    }
33  
34    /**
35     * Run a custom Cypher query to get a Stream of Timeseries including
36     *
37     * @param query The cypher query.
38     *              It needs to return a TimeseriesContainer named "tsc", a Timeseries "ts" and a TimeseriesTuple named "tst".
39     *              The Objects are required to be node entities known to neo4j ogm.
40     * @return Stream of Timeseries with related Objects.
41     */
42    private Stream<Timeseries> queryCypherWithRelations(String query) {
43      var result = session.query(query, Map.of(), true);
44      return StreamSupport.stream(result.spliterator(), false).map(resultEntry -> {
45        Timeseries tsObj = (Timeseries) resultEntry.get("ts");
46        TimeseriesContainer tscObj = (TimeseriesContainer) resultEntry.get("tsc");
47        TimeseriesTuple tsTupleObj = (TimeseriesTuple) resultEntry.get("tst");
48        tsObj.setContainer(tscObj);
49        tsObj.setTimeseriesTuple(tsTupleObj);
50        return tsObj;
51      });
52    }
53  
54    public Stream<Timeseries> getAllTimeseriesInContainer(long containerId) {
55      var tsc = node(TIMESERIES_CONTAINER).named("tsc");
56      var ts = node(TIMESERIES).named("ts");
57      var tsTuple = node(TIMESERIES_TUPLE).named("tst");
58      var isInContainer = ts.relationshipTo(tsc, IS_IN_CONTAINER);
59      var hasTuple = ts.relationshipTo(tsTuple, HAS_TIMESERIES_TUPLE);
60      var query = match(isInContainer, hasTuple)
61        .where(internalIdIs(tsc, containerId).and(notDeleted(ts)))
62        .returning(ts, tsc, tsTuple)
63        .build()
64        .getCypher();
65      return queryCypherWithRelations(query);
66    }
67  
68    public long getCurrentMaximumTimeseriesId() {
69      var ts = node(TIMESERIES);
70      var query = match(ts)
71        .returning(ts.property("timeseriesId"))
72        .orderBy(ts.property("timeseriesId").descending())
73        .limit(1)
74        .build()
75        .getCypher();
76      try {
77        return session.query(Long.class, query, Collections.emptyMap()).iterator().next();
78      } catch (NoSuchElementException e) {
79        // If no Timeseries is found we can assume a "fresh" database and the timeseries IDs can start anew.
80        return 0;
81      }
82    }
83  
84    public Optional<Timeseries> findTimeseries(long containerId, TimeseriesTuple tsTuple) {
85      var tsTupleNode = node(TIMESERIES_TUPLE)
86        .withProperties(
87          "measurement",
88          Cypher.literalOf(tsTuple.getMeasurement()),
89          "device",
90          Cypher.literalOf(tsTuple.getDevice()),
91          "location",
92          Cypher.literalOf(tsTuple.getLocation()),
93          "symbolicName",
94          Cypher.literalOf(tsTuple.getSymbolicName()),
95          "field",
96          Cypher.literalOf(tsTuple.getField())
97        )
98        .named("tst");
99      var tsc = node(TIMESERIES_CONTAINER).named("tsc");
100     var ts = node(TIMESERIES).named("ts");
101     var query = match(tsTupleNode.relationshipFrom(ts, HAS_TIMESERIES_TUPLE).relationshipTo(tsc, IS_IN_CONTAINER))
102       .where(internalIdIs(tsc, containerId).and(notDeleted(ts)))
103       .returning(tsTupleNode, tsc, ts)
104       .build()
105       .getCypher();
106 
107     return queryCypherWithRelations(query).findFirst();
108   }
109 
110   public Optional<Timeseries> findByTimeseriesId(long timeseriesId) {
111     var ts = node(TIMESERIES).withProperties("timeseriesId", Cypher.literalOf(timeseriesId));
112     var related = Cypher.anyNode();
113     var rels = ts.relationshipTo(related);
114     var query = match(ts, rels, related).where(notDeleted(ts)).returning(ts, rels, related).build().getCypher();
115     return this.findByQuery(query).findFirst();
116   }
117 
118   public void deleteAllTimeseriesInContainer(long containerId) {
119     var ts = node(TIMESERIES);
120     var tsc = node(TIMESERIES_CONTAINER);
121     var query = match(ts.relationshipTo(tsc, IS_IN_CONTAINER))
122       .where(internalIdIs(tsc, containerId))
123       .set(ts.property("deleted"), Cypher.literalOf(true))
124       .build()
125       .getCypher();
126     session.query(query, Collections.emptyMap());
127   }
128 }