Upgrade Tool: Split upgrade commands into individual classes

Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
pull/4718/head
Jimmy Tanagra 2025-04-27 22:43:14 +10:00 committed by Kai Kreuzer
parent d6fb61d0c7
commit 2515b05ee3
5 changed files with 349 additions and 161 deletions

View File

@ -12,8 +12,10 @@
*/ */
package org.openhab.core.tools; package org.openhab.core.tools;
import static org.openhab.core.tools.internal.Upgrader.*; import java.io.PrintStream;
import java.nio.file.Path;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
@ -23,27 +25,47 @@ import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options; import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException; import org.apache.commons.cli.ParseException;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.tools.internal.Upgrader; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.storage.json.internal.JsonStorage;
import org.openhab.core.tools.internal.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* The {@link UpgradeTool} is a tool for upgrading openHAB to mitigate breaking changes * The {@link UpgradeTool} is a tool for upgrading openHAB to mitigate breaking changes
* *
* @author Jan N. Klug - Initial contribution * @author Jan N. Klug - Initial contribution
* @author Jimmy Tanagra - Refactor upgraders into individual classes
*/ */
@NonNullByDefault @NonNullByDefault
public class UpgradeTool { public class UpgradeTool {
private static final Set<String> LOG_LEVELS = Set.of("TRACE", "DEBUG", "INFO", "WARN", "ERROR"); private static final Set<String> LOG_LEVELS = Set.of("TRACE", "DEBUG", "INFO", "WARN", "ERROR");
private static final String OPT_COMMAND = "command"; private static final String OPT_COMMAND = "command";
private static final String OPT_DIR = "dir"; private static final String OPT_LIST_COMMANDS = "list-commands";
private static final String OPT_USERDATA_DIR = "userdata";
private static final String OPT_CONF_DIR = "conf";
private static final String OPT_LOG = "log"; private static final String OPT_LOG = "log";
private static final String OPT_FORCE = "force"; private static final String OPT_FORCE = "force";
private static final List<Upgrader> UPGRADERS = List.of( //
new ItemUnitToMetadataUpgrader(), //
new JSProfileUpgrader(), //
new ScriptProfileUpgrader() //
);
private static final Logger logger = LoggerFactory.getLogger(UpgradeTool.class);
private static @Nullable JsonStorage<UpgradeRecord> upgradeRecords = null;
private static Options getOptions() { private static Options getOptions() {
Options options = new Options(); Options options = new Options();
options.addOption(Option.builder().longOpt(OPT_DIR).desc("directory to process").numberOfArgs(1).build()); options.addOption(Option.builder().longOpt(OPT_USERDATA_DIR).desc("USERDATA directory to process")
.numberOfArgs(1).build());
options.addOption(
Option.builder().longOpt(OPT_CONF_DIR).desc("CONF directory to process").numberOfArgs(1).build());
options.addOption(Option.builder().longOpt(OPT_COMMAND).numberOfArgs(1) options.addOption(Option.builder().longOpt(OPT_COMMAND).numberOfArgs(1)
.desc("command to execute (executes all if omitted)").build()); .desc("command to execute (executes all if omitted)").build());
options.addOption(Option.builder().longOpt(OPT_LIST_COMMANDS).desc("list available commands").build());
options.addOption(Option.builder().longOpt(OPT_LOG).numberOfArgs(1).desc("log verbosity").build()); options.addOption(Option.builder().longOpt(OPT_LOG).numberOfArgs(1).desc("log verbosity").build());
options.addOption(Option.builder().longOpt(OPT_FORCE).desc("force execution (even if already done)").build()); options.addOption(Option.builder().longOpt(OPT_FORCE).desc("force execution (even if already done)").build());
@ -58,42 +80,110 @@ public class UpgradeTool {
String loglevel = commandLine.hasOption(OPT_LOG) ? commandLine.getOptionValue(OPT_LOG).toUpperCase() String loglevel = commandLine.hasOption(OPT_LOG) ? commandLine.getOptionValue(OPT_LOG).toUpperCase()
: "INFO"; : "INFO";
if (!LOG_LEVELS.contains(loglevel)) { if (!LOG_LEVELS.contains(loglevel)) {
System.out.println("Allowed log-levels are " + LOG_LEVELS); println("Allowed log-levels are " + LOG_LEVELS);
System.exit(0);
}
if (commandLine.hasOption(OPT_LIST_COMMANDS)) {
println("Available commands:");
UPGRADERS.stream().forEach(upgrader -> {
println(" - " + upgrader.getName() + ": " + upgrader.getDescription());
});
System.exit(0); System.exit(0);
} }
System.setProperty(org.slf4j.simple.SimpleLogger.DEFAULT_LOG_LEVEL_KEY, loglevel); System.setProperty(org.slf4j.simple.SimpleLogger.DEFAULT_LOG_LEVEL_KEY, loglevel);
String baseDir = commandLine.hasOption(OPT_DIR) ? commandLine.getOptionValue(OPT_DIR) String userdataDir = commandLine.hasOption(OPT_USERDATA_DIR) ? commandLine.getOptionValue(OPT_USERDATA_DIR)
: System.getenv("OPENHAB_USERDATA"); : System.getenv("OPENHAB_USERDATA");
if (baseDir == null || baseDir.isBlank()) { if (userdataDir == null || userdataDir.isBlank()) {
System.out.println( println("Please either set the environment variable ${OPENHAB_USERDATA} or provide a directory through the --userdata option.");
"Please either set the environment variable ${OPENHAB_USERDATA} or provide a directory through the --dir option.");
System.exit(0); System.exit(0);
} else { return;
boolean force = commandLine.hasOption(OPT_FORCE); }
Upgrader upgrader = new Upgrader(baseDir, force); String confDir = commandLine.hasOption(OPT_CONF_DIR) ? commandLine.getOptionValue(OPT_CONF_DIR)
if (!commandLine.hasOption(OPT_COMMAND) : System.getenv("OPENHAB_CONF");
|| ITEM_COPY_UNIT_TO_METADATA.equals(commandLine.getOptionValue(OPT_COMMAND))) { if (confDir == null || confDir.isBlank()) {
upgrader.itemCopyUnitToMetadata(); println("Please either set the environment variable ${OPENHAB_CONF} or provide a directory through the --conf option.");
System.exit(0);
return;
} }
if (!commandLine.hasOption(OPT_COMMAND)
|| LINK_UPGRADE_JS_PROFILE.equals(commandLine.getOptionValue(OPT_COMMAND))) { boolean force = commandLine.hasOption(OPT_FORCE);
upgrader.linkUpgradeJsProfile(); String command = commandLine.hasOption(OPT_COMMAND) ? commandLine.getOptionValue(OPT_COMMAND) : null;
if (command != null && UPGRADERS.stream().filter(u -> u.getName().equals(command)).findAny().isEmpty()) {
println("Unknown command: " + command);
System.exit(0);
} }
if (!commandLine.hasOption(OPT_COMMAND)
|| LINK_UPGRADE_SCRIPT_PROFILE.equals(commandLine.getOptionValue(OPT_COMMAND))) { Path upgradeJsonDatabasePath = Path.of(userdataDir, "jsondb", "org.openhab.core.tools.UpgradeTool");
upgrader.linkUpgradeScriptProfile(); upgradeRecords = new JsonStorage<>(upgradeJsonDatabasePath.toFile(), null, 5, 0, 0, List.of());
UPGRADERS.forEach(upgrader -> {
String upgraderName = upgrader.getName();
if (command != null && !upgraderName.equals(command)) {
return;
} }
if (!force && lastExecuted(upgraderName) instanceof String executionDate) {
logger.info("Already executed '{}' on {}. Use '--force' to execute it again.", upgraderName,
executionDate);
return;
} }
try {
logger.info("Executing {}: {}", upgraderName, upgrader.getDescription());
if (upgrader.execute(userdataDir, confDir)) {
updateUpgradeRecord(upgraderName);
}
} catch (Exception e) {
logger.error("Error executing upgrader {}: {}", upgraderName, e.getMessage());
}
});
} catch (ParseException e) { } catch (ParseException e) {
HelpFormatter formatter = new HelpFormatter(); HelpFormatter formatter = new HelpFormatter();
String commands = Set.of(ITEM_COPY_UNIT_TO_METADATA, LINK_UPGRADE_JS_PROFILE, LINK_UPGRADE_SCRIPT_PROFILE) formatter.printHelp("upgradetool", "", options, "", true);
.toString();
formatter.printHelp("upgradetool", "", options, "Available commands: " + commands, true);
} }
System.exit(0); System.exit(0);
} }
private static @Nullable String lastExecuted(String upgrader) {
JsonStorage<UpgradeRecord> records = upgradeRecords;
if (records != null) {
UpgradeRecord upgradeRecord = records.get(upgrader);
if (upgradeRecord != null) {
return upgradeRecord.executionDate;
}
} else {
logger.error("Upgrade records storage is not initialized.");
}
return null;
}
private static void updateUpgradeRecord(String upgrader) {
JsonStorage<UpgradeRecord> records = upgradeRecords;
if (records != null) {
records.put(upgrader, new UpgradeRecord(ZonedDateTime.now()));
records.flush();
} else {
logger.error("Upgrade records storage is not initialized.");
}
}
// to avoid compiler's null pointer warnings
private static void println(String message) {
PrintStream out = System.out;
if (out != null) {
out.println(message);
}
}
private static class UpgradeRecord {
public final String executionDate;
public UpgradeRecord(ZonedDateTime executionDate) {
this.executionDate = executionDate.toString();
}
}
} }

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.tools;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link Upgrader} provides an interface for upgrading openHAB configuration files.
*
* Implementing class MUST provide a no-argument constructor.
*
* @author Jimmy Tanagra - Initial contribution
*/
@NonNullByDefault
public interface Upgrader {
String getName();
String getDescription();
/**
* Executes the upgrade process.
*
* @param userdataDir the OPENHAB_USERDATA directory for the upgrade,
* or a custom path given by the user as --userdata argument
* @param confDir the OPENHAB_CONF directory for the upgrade,
* or a custom path given by the user as --conf argument
* @return true if the upgrade was successful, false otherwise
*/
boolean execute(String userdataDir, String confDir);
}

View File

@ -18,77 +18,57 @@ import static org.openhab.core.thing.DefaultSystemChannelTypeProvider.SYSTEM_CHA
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.ZonedDateTime;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import javax.measure.Unit; import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.items.ManagedItemProvider; import org.openhab.core.items.ManagedItemProvider;
import org.openhab.core.items.Metadata; import org.openhab.core.items.Metadata;
import org.openhab.core.items.MetadataKey; import org.openhab.core.items.MetadataKey;
import org.openhab.core.library.items.NumberItem; import org.openhab.core.library.items.NumberItem;
import org.openhab.core.storage.json.internal.JsonStorage; import org.openhab.core.storage.json.internal.JsonStorage;
import org.openhab.core.thing.dto.ThingDTO; import org.openhab.core.thing.dto.ThingDTO;
import org.openhab.core.thing.internal.link.ItemChannelLinkConfigDescriptionProvider;
import org.openhab.core.thing.link.ItemChannelLink; import org.openhab.core.thing.link.ItemChannelLink;
import org.openhab.core.tools.Upgrader;
import org.openhab.core.types.util.UnitUtils; import org.openhab.core.types.util.UnitUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* The {@link Upgrader} contains the implementation of the upgrade methods * The {@link ItemUnitToMetadataUpgrader} copies the unit from the item to the metadata.
* *
* @author Jan N. Klug - Initial contribution * @author Jan N. Klug - Initial contribution
* @author Florian Hotze - Add script profile upgrade * @author Jimmy Tanagra - Refactored into a separate class
*/ */
@NonNullByDefault @NonNullByDefault
public class Upgrader { public class ItemUnitToMetadataUpgrader implements Upgrader {
public static final String ITEM_COPY_UNIT_TO_METADATA = "itemCopyUnitToMetadata"; private final Logger logger = LoggerFactory.getLogger(ItemUnitToMetadataUpgrader.class);
public static final String LINK_UPGRADE_JS_PROFILE = "linkUpgradeJsProfile";
public static final String LINK_UPGRADE_SCRIPT_PROFILE = "linkUpgradeScriptProfile";
private final Logger logger = LoggerFactory.getLogger(Upgrader.class); @Override
private final String baseDir; public String getName() {
private final boolean force; return "itemCopyUnitToMetadata"; // keep the old name for backwards compatibility
private final JsonStorage<UpgradeRecord> upgradeRecords;
public Upgrader(String baseDir, boolean force) {
this.baseDir = baseDir;
this.force = force;
Path upgradeJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.tools.UpgradeTool");
upgradeRecords = new JsonStorage<>(upgradeJsonDatabasePath.toFile(), null, 5, 0, 0, List.of());
} }
private boolean checkUpgradeRecord(String key) { @Override
UpgradeRecord upgradeRecord = upgradeRecords.get(key); public String getDescription() {
if (upgradeRecord != null && !force) { return "Copy item unit from state description to metadata";
logger.info("Already executed '{}' on {}. Use '--force' to execute it again.", key,
upgradeRecord.executionDate);
return false;
}
return true;
} }
public void itemCopyUnitToMetadata() { @Override
public boolean execute(String userdataDir, String confDir) {
boolean noLink; boolean noLink;
if (!checkUpgradeRecord(ITEM_COPY_UNIT_TO_METADATA)) { Path itemJsonDatabasePath = Path.of(userdataDir, "jsondb", "org.openhab.core.items.Item.json");
return; Path metadataJsonDatabasePath = Path.of(userdataDir, "jsondb", "org.openhab.core.items.Metadata.json");
} Path linkJsonDatabasePath = Path.of(userdataDir, "jsondb", "org.openhab.core.thing.link.ItemChannelLink.json");
Path itemJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.items.Item.json"); Path thingJsonDatabasePath = Path.of(userdataDir, "jsondb", "org.openhab.core.thing.Thing.json");
Path metadataJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.items.Metadata.json");
Path linkJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.thing.link.ItemChannelLink.json");
Path thingJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.thing.Thing.json");
logger.info("Copying item unit from state description to metadata in database '{}'", itemJsonDatabasePath); logger.info("Copying item unit from state description to metadata in database '{}'", itemJsonDatabasePath);
if (!Files.isReadable(itemJsonDatabasePath)) { if (!Files.isReadable(itemJsonDatabasePath)) {
logger.error("Cannot access item database '{}', check path and access rights.", itemJsonDatabasePath); logger.error("Cannot access item database '{}', check path and access rights.", itemJsonDatabasePath);
return; return false;
} }
if (!Files.isReadable(linkJsonDatabasePath) || !Files.isReadable(thingJsonDatabasePath)) { if (!Files.isReadable(linkJsonDatabasePath) || !Files.isReadable(thingJsonDatabasePath)) {
@ -102,7 +82,7 @@ public class Upgrader {
if (!Files.isWritable(metadataJsonDatabasePath) && Files.exists(metadataJsonDatabasePath)) { if (!Files.isWritable(metadataJsonDatabasePath) && Files.exists(metadataJsonDatabasePath)) {
logger.error("Cannot access metadata database '{}', check path and access rights.", logger.error("Cannot access metadata database '{}', check path and access rights.",
metadataJsonDatabasePath); metadataJsonDatabasePath);
return; return false;
} }
JsonStorage<ManagedItemProvider.PersistedItem> itemStorage = new JsonStorage<>(itemJsonDatabasePath.toFile(), JsonStorage<ManagedItemProvider.PersistedItem> itemStorage = new JsonStorage<>(itemJsonDatabasePath.toFile(),
@ -121,7 +101,7 @@ public class Upgrader {
logger.debug("{}: Already contains a 'unit' metadata, skipping it", itemName); logger.debug("{}: Already contains a 'unit' metadata, skipping it", itemName);
} else { } else {
String unit = null; String unit = null;
if (!noLink) { if (linkStorage != null && thingStorage != null) {
List<ItemChannelLink> links = linkStorage.getValues().stream().map(Objects::requireNonNull) List<ItemChannelLink> links = linkStorage.getValues().stream().map(Objects::requireNonNull)
.filter(link -> itemName.equals(link.getItemName())).toList(); .filter(link -> itemName.equals(link.getItemName())).toList();
// check if we can find the channel for these links // check if we can find the channel for these links
@ -192,98 +172,7 @@ public class Upgrader {
}); });
metadataStorage.flush(); metadataStorage.flush();
upgradeRecords.put(ITEM_COPY_UNIT_TO_METADATA, new UpgradeRecord(ZonedDateTime.now()));
upgradeRecords.flush();
}
public void linkUpgradeJsProfile() { return true;
if (!checkUpgradeRecord(LINK_UPGRADE_JS_PROFILE)) {
return;
}
Path linkJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.thing.link.ItemChannelLink.json");
logger.info("Upgrading JS profile configuration in database '{}'", linkJsonDatabasePath);
if (!Files.isWritable(linkJsonDatabasePath)) {
logger.error("Cannot access link database '{}', check path and access rights.", linkJsonDatabasePath);
return;
}
JsonStorage<ItemChannelLink> linkStorage = new JsonStorage<>(linkJsonDatabasePath.toFile(), null, 5, 0, 0,
List.of());
List.copyOf(linkStorage.getKeys()).forEach(linkUid -> {
ItemChannelLink link = Objects.requireNonNull(linkStorage.get(linkUid));
Configuration configuration = link.getConfiguration();
String profileName = (String) configuration.get(ItemChannelLinkConfigDescriptionProvider.PARAM_PROFILE);
if ("transform:JS".equals(profileName)) {
String function = (String) configuration.get("function");
if (function != null) {
configuration.put("toItemScript", function);
configuration.put("toHandlerScript", "|input");
configuration.remove("function");
configuration.remove("sourceFormat");
linkStorage.put(linkUid, link);
logger.info("{}: rewrote JS profile link to new format", linkUid);
} else {
logger.info("{}: link already has correct configuration", linkUid);
}
}
});
linkStorage.flush();
upgradeRecords.put(LINK_UPGRADE_JS_PROFILE, new UpgradeRecord(ZonedDateTime.now()));
upgradeRecords.flush();
}
/**
* Upgrades the ItemChannelLink database for the separation of {@code toHandlerScript} into
* {@code commandFromItemScript} and {@code stateFromItemScript}.
* See <a href="https://github.com/openhab/openhab-core/pull/4058">openhab/openhab-core#4058</a>.
*/
public void linkUpgradeScriptProfile() {
if (!checkUpgradeRecord(LINK_UPGRADE_SCRIPT_PROFILE)) {
return;
}
Path linkJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.thing.link.ItemChannelLink.json");
logger.info("Upgrading script profile configuration in database '{}'", linkJsonDatabasePath);
if (!Files.isWritable(linkJsonDatabasePath)) {
logger.error("Cannot access link database '{}', check path and access rights.", linkJsonDatabasePath);
return;
}
JsonStorage<ItemChannelLink> linkStorage = new JsonStorage<>(linkJsonDatabasePath.toFile(), null, 5, 0, 0,
List.of());
List.copyOf(linkStorage.getKeys()).forEach(linkUid -> {
ItemChannelLink link = Objects.requireNonNull(linkStorage.get(linkUid));
Configuration configuration = link.getConfiguration();
String profileName = (String) configuration.get(ItemChannelLinkConfigDescriptionProvider.PARAM_PROFILE);
if (profileName != null && profileName.startsWith("transform:")) {
String toHandlerScript = (String) configuration.get("toHandlerScript");
if (toHandlerScript != null) {
configuration.put("commandFromItemScript", toHandlerScript);
configuration.remove("toHandlerScript");
linkStorage.put(linkUid, link);
logger.info("{}: rewrote script profile link to new format", linkUid);
} else {
logger.info("{}: link already has correct configuration", linkUid);
}
}
});
linkStorage.flush();
upgradeRecords.put(LINK_UPGRADE_SCRIPT_PROFILE, new UpgradeRecord(ZonedDateTime.now()));
upgradeRecords.flush();
}
private static class UpgradeRecord {
public final String executionDate;
public UpgradeRecord(ZonedDateTime executionDate) {
this.executionDate = executionDate.toString();
}
} }
} }

View File

@ -0,0 +1,84 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.tools.internal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.storage.json.internal.JsonStorage;
import org.openhab.core.thing.internal.link.ItemChannelLinkConfigDescriptionProvider;
import org.openhab.core.thing.link.ItemChannelLink;
import org.openhab.core.tools.Upgrader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link JSProfileUpgrader} upgrades JS Profile configurations
*
* @author Jan N. Klug - Initial contribution
* @author Jimmy Tanagra - Refactored into a separate class
*/
@NonNullByDefault
public class JSProfileUpgrader implements Upgrader {
private final Logger logger = LoggerFactory.getLogger(JSProfileUpgrader.class);
@Override
public String getName() {
return "linkUpgradeJSProfile"; // keep the old name for backwards compatibility
}
@Override
public String getDescription() {
return "Upgrade JS profile configuration to new format";
}
@Override
public boolean execute(String userdataDir, String confDir) {
Path linkJsonDatabasePath = Path.of(userdataDir, "jsondb", "org.openhab.core.thing.link.ItemChannelLink.json");
logger.info("Upgrading JS profile configuration in database '{}'", linkJsonDatabasePath);
if (!Files.isWritable(linkJsonDatabasePath)) {
logger.error("Cannot access link database '{}', check path and access rights.", linkJsonDatabasePath);
return false;
}
JsonStorage<ItemChannelLink> linkStorage = new JsonStorage<>(linkJsonDatabasePath.toFile(), null, 5, 0, 0,
List.of());
List.copyOf(linkStorage.getKeys()).forEach(linkUid -> {
ItemChannelLink link = Objects.requireNonNull(linkStorage.get(linkUid));
Configuration configuration = link.getConfiguration();
String profileName = (String) configuration.get(ItemChannelLinkConfigDescriptionProvider.PARAM_PROFILE);
if ("transform:JS".equals(profileName)) {
String function = (String) configuration.get("function");
if (function != null) {
configuration.put("toItemScript", function);
configuration.put("toHandlerScript", "|input");
configuration.remove("function");
configuration.remove("sourceFormat");
linkStorage.put(linkUid, link);
logger.info("{}: rewrote JS profile link to new format", linkUid);
} else {
logger.info("{}: link already has correct configuration", linkUid);
}
}
});
linkStorage.flush();
return true;
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.tools.internal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.storage.json.internal.JsonStorage;
import org.openhab.core.thing.internal.link.ItemChannelLinkConfigDescriptionProvider;
import org.openhab.core.thing.link.ItemChannelLink;
import org.openhab.core.tools.Upgrader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ScriptProfileUpgrader} upgrades the ItemChannelLink database
* for the separation of {@code toHandlerScript} into
* {@code commandFromItemScript} and {@code stateFromItemScript}.
* See <a href="https://github.com/openhab/openhab-core/pull/4058">openhab/openhab-core#4058</a>.
*
* @author Florian Hotze - Initial contribution
* @author Jimmy Tanagra - Refactored into a separate class
*/
@NonNullByDefault
public class ScriptProfileUpgrader implements Upgrader {
private final Logger logger = LoggerFactory.getLogger(ScriptProfileUpgrader.class);
@Override
public String getName() {
return "linkUpgradeScriptProfile"; // keep the old name for backwards compatibility
}
@Override
public String getDescription() {
return "Upgrade script profile configuration toHandlerScript to commandFromItemScript";
}
@Override
public boolean execute(String userdataDir, String confDir) {
Path linkJsonDatabasePath = Path.of(userdataDir, "jsondb", "org.openhab.core.thing.link.ItemChannelLink.json");
logger.info("Upgrading script profile configuration in database '{}'", linkJsonDatabasePath);
if (!Files.isWritable(linkJsonDatabasePath)) {
logger.error("Cannot access link database '{}', check path and access rights.", linkJsonDatabasePath);
return false;
}
JsonStorage<ItemChannelLink> linkStorage = new JsonStorage<>(linkJsonDatabasePath.toFile(), null, 5, 0, 0,
List.of());
List.copyOf(linkStorage.getKeys()).forEach(linkUid -> {
ItemChannelLink link = Objects.requireNonNull(linkStorage.get(linkUid));
Configuration configuration = link.getConfiguration();
String profileName = (String) configuration.get(ItemChannelLinkConfigDescriptionProvider.PARAM_PROFILE);
if (profileName != null && profileName.startsWith("transform:")) {
String toHandlerScript = (String) configuration.get("toHandlerScript");
if (toHandlerScript != null) {
configuration.put("commandFromItemScript", toHandlerScript);
configuration.remove("toHandlerScript");
linkStorage.put(linkUid, link);
logger.info("{}: rewrote script profile link to new format", linkUid);
} else {
logger.info("{}: link already has correct configuration", linkUid);
}
}
});
linkStorage.flush();
return true;
}
}