-
-
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
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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, | ||
"Missing or invalid configuration.")); | ||
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. As the 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. Where Do I have to place the correct value then? 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. I think it should be placed into a new properties file with name |
||
} | ||
} | ||
} 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, "Missing or invalid configuration.")); | ||
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. Use the same custom key here. |
||
} | ||
} 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) { | ||
|
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.