]> git.basschouten.com Git - openhab-addons.git/blob
fa2a8960445d5c39df33749e2928549cbc614b0f
[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         DatabaseBridgeHandler databaseBridgeHandler;
197
198         if (bridge != null) {
199             @Nullable
200             BridgeHandler bridgeHandler = bridge.getHandler();
201             if (bridgeHandler instanceof DatabaseBridgeHandler) {
202                 databaseBridgeHandler = (DatabaseBridgeHandler) bridgeHandler;
203                 database = databaseBridgeHandler.getDatabase();
204                 if (bridge.getStatus() == ThingStatus.ONLINE) {
205                     updateStatus(ThingStatus.ONLINE);
206                 } else {
207                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
208                 }
209             } else {
210                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
211             }
212         } else {
213             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
214         }
215     }
216
217     @Override
218     protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
219         super.updateStatus(status, statusDetail, description);
220         if (status == ThingStatus.ONLINE) {
221             scheduleQueryExecutionIntervalIfNeeded();
222         }
223     }
224
225     @Override
226     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
227         cancelCurrentQueryExecution();
228         updateStateWithParentBridgeStatus();
229     }
230
231     public void setParameters(Map<String, @Nullable Object> parameters) {
232         final @Nullable QueryExecution queryExecution = currentQueryExecution;
233         if (queryExecution != null) {
234             QueryParameters queryParameters = new QueryParameters(parameters);
235             queryExecution.setQueryParameters(queryParameters);
236             queryExecution.execute();
237         } else {
238             logger.trace("setParameters ignored as there is any executing query for {}", getQueryIdentifier());
239         }
240     }
241
242     private final QueryExecution.QueryResultListener queryResultReceived = (QueryResult queryResult) -> {
243         synchronized (QueryHandler.this) {
244             logger.trace("queryResultReceived for {} : {}", getQueryIdentifier(), queryResult);
245             updateStateWithQueryResult(queryResult);
246
247             currentQueryExecution = null;
248         }
249     };
250
251     @Override
252     public Collection<Class<? extends ThingHandlerService>> getServices() {
253         return List.of(DBQueryActions.class);
254     }
255
256     public QueryResult getLastQueryResult() {
257         return lastQueryResult;
258     }
259
260     private List<Channel> getResultChannels2Update() {
261         return getThing().getChannels().stream().filter(channel -> isLinked(channel.getUID()))
262                 .filter(this::isResultChannel).collect(Collectors.toList());
263     }
264
265     private boolean isResultChannel(Channel channel) {
266         @Nullable
267         ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
268         return channelTypeUID != null && channelTypeUID.getId().startsWith("result");
269     }
270 }