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