]> git.basschouten.com Git - openhab-addons.git/blob
6e6762f7ea810e193ce63668015e4d82f3fe86bb
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.dbquery.internal;
14
15 import static org.openhab.binding.dbquery.internal.DBQueryBindingConstants.CHANNEL_EXECUTE;
16 import static org.openhab.binding.dbquery.internal.DBQueryBindingConstants.TRIGGER_CHANNEL_CALCULATE_PARAMETERS;
17
18 import java.util.Collection;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23 import java.util.stream.Collectors;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.dbquery.action.DBQueryActions;
28 import org.openhab.binding.dbquery.internal.config.QueryConfiguration;
29 import org.openhab.binding.dbquery.internal.domain.DBQueryJSONEncoder;
30 import org.openhab.binding.dbquery.internal.domain.Database;
31 import org.openhab.binding.dbquery.internal.domain.QueryParameters;
32 import org.openhab.binding.dbquery.internal.domain.QueryResult;
33 import org.openhab.binding.dbquery.internal.domain.QueryResultExtractor;
34 import org.openhab.binding.dbquery.internal.domain.ResultValue;
35 import org.openhab.core.library.types.OnOffType;
36 import org.openhab.core.library.types.StringType;
37 import org.openhab.core.thing.Bridge;
38 import org.openhab.core.thing.Channel;
39 import org.openhab.core.thing.ChannelUID;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.ThingStatusDetail;
43 import org.openhab.core.thing.ThingStatusInfo;
44 import org.openhab.core.thing.ThingUID;
45 import org.openhab.core.thing.binding.BaseThingHandler;
46 import org.openhab.core.thing.binding.BridgeHandler;
47 import org.openhab.core.thing.binding.ThingHandlerService;
48 import org.openhab.core.thing.type.ChannelTypeUID;
49 import org.openhab.core.types.Command;
50 import org.openhab.core.types.RefreshType;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 /**
55  * Manages query thing, handling it's commands and updating it's channels
56  *
57  * @author Joan Pujol - Initial contribution
58  */
59 @NonNullByDefault
60 public class QueryHandler extends BaseThingHandler {
61
62     private final Logger logger = LoggerFactory.getLogger(QueryHandler.class);
63     // Relax nullable rules as config can be only null when not initialized
64     private @NonNullByDefault({}) QueryConfiguration config;
65     private @NonNullByDefault({}) QueryResultExtractor queryResultExtractor;
66
67     private @Nullable ScheduledFuture<?> scheduledQueryExecutionInterval;
68     private @Nullable QueryResultChannelUpdater queryResultChannelUpdater;
69     private Database database = Database.EMPTY;
70     private final DBQueryJSONEncoder jsonEncoder = new DBQueryJSONEncoder();
71
72     private @Nullable QueryExecution currentQueryExecution;
73     private QueryResult lastQueryResult = QueryResult.NO_RESULT;
74
75     public QueryHandler(Thing thing) {
76         super(thing);
77     }
78
79     @Override
80     public void initialize() {
81         config = getConfigAs(QueryConfiguration.class);
82         queryResultExtractor = new QueryResultExtractor(config);
83
84         initQueryResultChannelUpdater();
85         updateStateWithParentBridgeStatus();
86     }
87
88     private void initQueryResultChannelUpdater() {
89         ChannelStateUpdater channelStateUpdater = (channel, state) -> updateState(channel.getUID(), state);
90         queryResultChannelUpdater = new QueryResultChannelUpdater(channelStateUpdater, this::getResultChannels2Update);
91     }
92
93     private void scheduleQueryExecutionIntervalIfNeeded() {
94         int interval = config.getInterval();
95         if (interval != QueryConfiguration.NO_INTERVAL && scheduledQueryExecutionInterval == null) {
96             logger.trace("Scheduling query execution every {} seconds for {}", interval, getQueryIdentifier());
97             scheduledQueryExecutionInterval = scheduler.scheduleWithFixedDelay(this::executeQuery, 0, interval,
98                     TimeUnit.SECONDS);
99         }
100     }
101
102     private ThingUID getQueryIdentifier() {
103         return getThing().getUID();
104     }
105
106     private void cancelQueryExecutionIntervalIfNeeded() {
107         ScheduledFuture<?> currentFuture = scheduledQueryExecutionInterval;
108         if (currentFuture != null) {
109             currentFuture.cancel(true);
110             scheduledQueryExecutionInterval = null;
111         }
112     }
113
114     @Override
115     public void dispose() {
116         cancelQueryExecutionIntervalIfNeeded();
117         cancelCurrentQueryExecution();
118         super.dispose();
119     }
120
121     @Override
122     public void handleCommand(ChannelUID channelUID, Command command) {
123         logger.trace("handleCommand for channel {} with command {}", channelUID, command);
124
125         if (command instanceof RefreshType) {
126             if (CHANNEL_EXECUTE.equals(channelUID.getId())) {
127                 executeQuery();
128             }
129         } else {
130             logger.warn("Query Thing can only handle RefreshType commands as the thing is read-only");
131         }
132     }
133
134     private synchronized void executeQuery() {
135         if (getThing().getStatus() == ThingStatus.ONLINE) {
136             QueryExecution queryExecution = currentQueryExecution;
137             if (queryExecution != null) {
138                 logger.debug("Previous query execution for {} discarded as a new one is requested",
139                         getQueryIdentifier());
140                 cancelCurrentQueryExecution();
141             }
142
143             queryExecution = new QueryExecution(database, config, queryResultReceived);
144             this.currentQueryExecution = queryExecution;
145
146             if (config.isHasParameters()) {
147                 logger.trace("{} triggered to set parameters for {}", TRIGGER_CHANNEL_CALCULATE_PARAMETERS,
148                         queryExecution);
149                 updateParametersChannel(QueryParameters.EMPTY);
150                 triggerChannel(TRIGGER_CHANNEL_CALCULATE_PARAMETERS);
151             } else {
152                 queryExecution.execute();
153             }
154         } else {
155             logger.debug("Execute query ignored because thing status is {}", getThing().getStatus());
156         }
157     }
158
159     private synchronized void cancelCurrentQueryExecution() {
160         QueryExecution current = currentQueryExecution;
161         if (current != null) {
162             current.cancel();
163             currentQueryExecution = null;
164         }
165     }
166
167     private void updateStateWithQueryResult(QueryResult queryResult) {
168         var currentQueryResultChannelUpdater = queryResultChannelUpdater;
169         var localCurrentQueryExecution = this.currentQueryExecution;
170         lastQueryResult = queryResult;
171         if (currentQueryResultChannelUpdater != null && localCurrentQueryExecution != null) {
172             ResultValue resultValue = queryResultExtractor.extractResult(queryResult);
173             updateCorrectChannel(resultValue.isCorrect());
174             updateParametersChannel(localCurrentQueryExecution.getQueryParameters());
175             if (resultValue.isCorrect()) {
176                 currentQueryResultChannelUpdater.updateChannelResults(resultValue.getResult());
177             } else {
178                 currentQueryResultChannelUpdater.clearChannelResults();
179             }
180         } else {
181             logger.warn(
182                     "QueryResult discarded as queryResultChannelUpdater nor currentQueryExecution are not expected to be null");
183         }
184     }
185
186     private void updateCorrectChannel(boolean correct) {
187         updateState(DBQueryBindingConstants.CHANNEL_CORRECT, OnOffType.from(correct));
188     }
189
190     private void updateParametersChannel(QueryParameters queryParameters) {
191         updateState(DBQueryBindingConstants.CHANNEL_PARAMETERS, new StringType(jsonEncoder.encode(queryParameters)));
192     }
193
194     private void updateStateWithParentBridgeStatus() {
195         final @Nullable Bridge bridge = getBridge();
196
197         if (bridge != null) {
198             @Nullable
199             BridgeHandler bridgeHandler = bridge.getHandler();
200             if (bridgeHandler instanceof DatabaseBridgeHandler databaseBridgeHandler) {
201                 database = databaseBridgeHandler.getDatabase();
202                 if (bridge.getStatus() == ThingStatus.ONLINE) {
203                     updateStatus(ThingStatus.ONLINE);
204                 } else {
205                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
206                 }
207             } else {
208                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
209             }
210         } else {
211             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
212         }
213     }
214
215     @Override
216     protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
217         super.updateStatus(status, statusDetail, description);
218         if (status == ThingStatus.ONLINE) {
219             scheduleQueryExecutionIntervalIfNeeded();
220         }
221     }
222
223     @Override
224     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
225         cancelCurrentQueryExecution();
226         updateStateWithParentBridgeStatus();
227     }
228
229     public void setParameters(Map<String, @Nullable Object> parameters) {
230         final @Nullable QueryExecution queryExecution = currentQueryExecution;
231         if (queryExecution != null) {
232             QueryParameters queryParameters = new QueryParameters(parameters);
233             queryExecution.setQueryParameters(queryParameters);
234             queryExecution.execute();
235         } else {
236             logger.trace("setParameters ignored as there is any executing query for {}", getQueryIdentifier());
237         }
238     }
239
240     private final QueryExecution.QueryResultListener queryResultReceived = (QueryResult queryResult) -> {
241         synchronized (QueryHandler.this) {
242             logger.trace("queryResultReceived for {} : {}", getQueryIdentifier(), queryResult);
243             updateStateWithQueryResult(queryResult);
244
245             currentQueryExecution = null;
246         }
247     };
248
249     @Override
250     public Collection<Class<? extends ThingHandlerService>> getServices() {
251         return List.of(DBQueryActions.class);
252     }
253
254     public QueryResult getLastQueryResult() {
255         return lastQueryResult;
256     }
257
258     private List<Channel> getResultChannels2Update() {
259         return getThing().getChannels().stream().filter(channel -> isLinked(channel.getUID()))
260                 .filter(this::isResultChannel).collect(Collectors.toList());
261     }
262
263     private boolean isResultChannel(Channel channel) {
264         @Nullable
265         ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
266         return channelTypeUID != null && channelTypeUID.getId().startsWith("result");
267     }
268 }