-
-
Notifications
You must be signed in to change notification settings - Fork 429
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
enable config validation for thing creation and update #2682
Merged
cweitkamp
merged 3 commits into
openhab:main
from
J-N-K:feature-thing-config-validation
Jan 20, 2022
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,7 +21,6 @@ | |
import java.util.HashMap; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Locale; | ||
import java.util.Map; | ||
import java.util.Map.Entry; | ||
import java.util.Set; | ||
|
@@ -42,11 +41,10 @@ | |
import org.openhab.core.common.registry.Identifiable; | ||
import org.openhab.core.common.registry.ManagedProvider; | ||
import org.openhab.core.common.registry.Provider; | ||
import org.openhab.core.config.core.ConfigDescription; | ||
import org.openhab.core.config.core.ConfigDescriptionParameter; | ||
import org.openhab.core.config.core.ConfigDescriptionRegistry; | ||
import org.openhab.core.config.core.Configuration; | ||
import org.openhab.core.config.core.validation.ConfigDescriptionValidator; | ||
import org.openhab.core.config.core.validation.ConfigValidationException; | ||
import org.openhab.core.events.EventPublisher; | ||
import org.openhab.core.service.ReadyMarker; | ||
import org.openhab.core.service.ReadyMarkerFilter; | ||
|
@@ -558,6 +556,7 @@ public void thingUpdated(final Thing thing, ThingTrackerEvent thingTrackerEvent) | |
// called from the thing handler itself, therefore | ||
// it exists, is initializing/initialized and | ||
// must not be informed (in order to prevent infinite loops) | ||
// we assume the handler knows what he's doing and don't check config validity | ||
replaceThing(getThing(thingUID), thing); | ||
} else { | ||
Lock lock1 = getLockForThing(thing.getUID()); | ||
|
@@ -571,7 +570,23 @@ public void thingUpdated(final Thing thing, ThingTrackerEvent thingTrackerEvent) | |
oldThing.setHandler(null); | ||
} | ||
thing.setHandler(thingHandler); | ||
safeCaller.create(thingHandler, ThingHandler.class).build().thingUpdated(thing); | ||
|
||
if (isInitializable(thing, getThingType(thing))) { | ||
safeCaller.create(thingHandler, ThingHandler.class).build().thingUpdated(thing); | ||
} else { | ||
final ThingHandlerFactory thingHandlerFactory = findThingHandlerFactory( | ||
thing.getThingTypeUID()); | ||
if (thingHandlerFactory != null) { | ||
if (isBridge(thing)) { | ||
unregisterAndDisposeChildHandlers((Bridge) thing, thingHandlerFactory); | ||
} | ||
disposeHandler(thing, thingHandler); | ||
setThingStatus(thing, | ||
buildStatusInfo(ThingStatus.UNINITIALIZED, | ||
ThingStatusDetail.HANDLER_CONFIGURATION_PENDING, | ||
"@text/missing-or-invalid-configuration")); | ||
} | ||
} | ||
} else { | ||
logger.debug( | ||
"Cannot notify handler about updated thing '{}', because handler is not initialized (thing must be in status UNKNOWN, ONLINE or OFFLINE).", | ||
|
@@ -744,23 +759,25 @@ private void initializeHandler(Thing thing) { | |
setThingStatus(thing, buildStatusInfo(ThingStatus.INITIALIZING, ThingStatusDetail.NONE)); | ||
doInitializeHandler(handler); | ||
} else { | ||
logger.debug("Thing '{}' not initializable, check required configuration parameters.", thing.getUID()); | ||
setThingStatus(thing, | ||
buildStatusInfo(ThingStatus.UNINITIALIZED, ThingStatusDetail.HANDLER_CONFIGURATION_PENDING)); | ||
logger.debug( | ||
"Thing '{}' not initializable, check if required configuration parameters are present and set values are valid.", | ||
thing.getUID()); | ||
setThingStatus(thing, buildStatusInfo(ThingStatus.UNINITIALIZED, | ||
ThingStatusDetail.HANDLER_CONFIGURATION_PENDING, "@text/missing-or-invalid-configuration")); | ||
} | ||
} finally { | ||
lock.unlock(); | ||
} | ||
} | ||
|
||
private boolean isInitializable(Thing thing, @Nullable ThingType thingType) { | ||
if (!isComplete(thingType, thing.getUID(), tt -> tt.getConfigDescriptionURI(), thing.getConfiguration())) { | ||
if (!isComplete(thingType, thing.getUID(), ThingType::getConfigDescriptionURI, thing.getConfiguration())) { | ||
return false; | ||
} | ||
|
||
for (Channel channel : thing.getChannels()) { | ||
ChannelType channelType = channelTypeRegistry.getChannelType(channel.getChannelTypeUID()); | ||
if (!isComplete(channelType, channel.getUID(), ct -> ct.getConfigDescriptionURI(), | ||
if (!isComplete(channelType, channel.getUID(), ChannelType::getConfigDescriptionURI, | ||
channel.getConfiguration())) { | ||
return false; | ||
} | ||
|
@@ -786,35 +803,20 @@ private <T extends Identifiable<?>> boolean isComplete(@Nullable T prototype, UI | |
return true; | ||
} | ||
|
||
ConfigDescription description = resolve(configDescriptionURIFunction.apply(prototype), null); | ||
if (description == null) { | ||
logger.debug("Config description for '{}' is not resolvable, assuming '{}' is initializable", | ||
URI configDescriptionURI = configDescriptionURIFunction.apply(prototype); | ||
if (configDescriptionURI == null) { | ||
logger.debug("Config description URI for '{}' not found, assuming '{}' is initializable", | ||
prototype.getUID(), targetUID); | ||
return true; | ||
} | ||
|
||
List<String> requiredParameters = getRequiredParameters(description); | ||
Set<String> propertyKeys = configuration.getProperties().keySet(); | ||
if (logger.isDebugEnabled()) { | ||
logger.debug("Configuration of '{}' needs {}, has {}.", targetUID, requiredParameters, propertyKeys); | ||
try { | ||
configDescriptionValidator.validate(configuration.getProperties(), configDescriptionURI); | ||
} catch (ConfigValidationException e) { | ||
return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we add a trace log here to allow users to see validation messages in the log file? This might help to find the culprit for #2710. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See #2711 |
||
} | ||
return propertyKeys.containsAll(requiredParameters); | ||
} | ||
|
||
private @Nullable ConfigDescription resolve(@Nullable URI configDescriptionURI, @Nullable Locale locale) { | ||
return configDescriptionURI != null | ||
? configDescriptionRegistry.getConfigDescription(configDescriptionURI, locale) | ||
: null; | ||
} | ||
|
||
private List<String> getRequiredParameters(ConfigDescription description) { | ||
List<String> requiredParameters = new ArrayList<>(); | ||
for (ConfigDescriptionParameter param : description.getParameters()) { | ||
if (param.isRequired()) { | ||
requiredParameters.add(param.getName()); | ||
} | ||
} | ||
return requiredParameters; | ||
return true; | ||
} | ||
|
||
private void doInitializeHandler(final ThingHandler thingHandler) { | ||
|
1 change: 1 addition & 0 deletions
1
...s/org.openhab.core.thing/src/main/resources/OH-INF/i18n/SystemThingStatusInfos.properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
missing-or-invalid-configuration=Missing or invalid configuration. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that this assumption is wrong as e.g. the ZWave handler updates the configuration with values outside of the valid range. @cweitkamp shall we add a validation here, too? And what shall happen if the thing handler tries to update with an invalid configuration? Log a warning and deny replacing the thing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I agree. Both methods in
BaseThingHandler
which allows to update the configuration of a thing (updateThing(Thing)
andupdateConfiguration(Configuration)
) lead to an unchecked configuration. There is no validation done.Both
updateThing()
andupdateConfiguration()
callThingHandlerCallback.thingUpdated()
->ThingRegistry.updated()
->ThingTracker.thingUpdated()
ofThingManagerImpl
.In theorie we have to to check for a changed configuration of the thing and do the same like the
ThingResource
: callThingRegistry.updateConfiguration()
instead of just replacing the thing here. imo the handler has to be reinitialized if configuration will change.Or did I miss something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the problem with this assumption is that the binding doesn't have access to the config description, so it isn't (easily) possible for it to do this as far as I can see. The framework only passes the values to the binding - maybe it should also make the descriptions more readily available as well now that we're enforcing these checks.