Initial contribution of an CLI upgrade-tool (#3268)

* Initial contribution of an upgrade-tool

Signed-off-by: Jan N. Klug <github@klug.nrw>
This commit is contained in:
J-N-K 2023-05-01 21:58:11 +02:00 committed by GitHub
parent cdab53b814
commit c40dd73d77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 379 additions and 0 deletions

View File

@ -19,6 +19,7 @@
<modules>
<module>archetype</module>
<module>i18n-plugin</module>
<module>upgradetool</module>
</modules>
</project>

123
tools/upgradetool/pom.xml Normal file
View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.core.tools</groupId>
<artifactId>org.openhab.core.reactor.tools</artifactId>
<version>4.0.0-SNAPSHOT</version>
</parent>
<artifactId>upgradetool</artifactId>
<packaging>jar</packaging>
<name>openHAB Core :: Tools :: Upgrade tool</name>
<description>A tool for upgrading openHAB from 3.4 to 4.0</description>
<dependencies>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.thing</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.storage.json</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>javax.measure</groupId>
<artifactId>unit-api</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>si.uom</groupId>
<artifactId>si-units</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>tech.units</groupId>
<artifactId>indriya</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>org.eclipse.jdt.annotation</artifactId>
<version>2.2.600</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>unpack-eea</id>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.lastnpe.eea</groupId>
<artifactId>eea-all</artifactId>
<version>${eea.version}</version>
<overWrite>true</overWrite>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<archive>
<manifest>
<mainClass>org.openhab.core.tools.UpgradeTool</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<goals>
<goal>single</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,84 @@
/**
* Copyright (c) 2010-2023 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 static org.openhab.core.tools.internal.Upgrader.*;
import java.util.Set;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.tools.internal.Upgrader;
/**
* The {@link UpgradeTool} is a tool for upgrading openHAB to mitigate breaking changes
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class UpgradeTool {
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_DIR = "dir";
private static final String OPT_LOG = "log";
private static final String OPT_FORCE = "force";
private static Options getOptions() {
Options options = new Options();
options.addOption(
Option.builder().longOpt(OPT_DIR).desc("directory to process").numberOfArgs(1).required().build());
options.addOption(Option.builder().longOpt(OPT_COMMAND).numberOfArgs(1).desc("command to execute").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());
return options;
}
public static void main(String[] args) {
Options options = getOptions();
try {
CommandLine commandLine = new DefaultParser().parse(options, args);
String loglevel = commandLine.hasOption(OPT_LOG) ? commandLine.getOptionValue(OPT_LOG).toUpperCase()
: "INFO";
if (!LOG_LEVELS.contains(loglevel)) {
System.out.println("Allowed log-levels are " + LOG_LEVELS);
System.exit(0);
}
System.setProperty(org.slf4j.impl.SimpleLogger.DEFAULT_LOG_LEVEL_KEY, loglevel);
String baseDir = commandLine.hasOption(OPT_DIR) ? commandLine.getOptionValue(OPT_DIR) : "";
boolean force = commandLine.hasOption(OPT_FORCE) ? true : false;
Upgrader upgrader = new Upgrader(baseDir, force);
if (commandLine.hasOption(ITEM_COPY_UNIT_TO_METADATA)) {
upgrader.itemCopyUnitToMetadata();
} else if (commandLine.hasOption(LINK_UPGRADE_JS_PROFILE)) {
upgrader.linkUpgradeJsProfile();
}
} catch (ParseException e) {
HelpFormatter formatter = new HelpFormatter();
String commands = Set.of(ITEM_COPY_UNIT_TO_METADATA, LINK_UPGRADE_JS_PROFILE).toString();
formatter.printHelp("upgradetool", "", options, "Available commands: " + commands, true);
}
System.exit(0);
}
}

View File

@ -0,0 +1,171 @@
/**
* Copyright (c) 2010-2023 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.time.ZonedDateTime;
import java.util.List;
import java.util.Objects;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.items.ManagedItemProvider;
import org.openhab.core.items.Metadata;
import org.openhab.core.items.MetadataKey;
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.types.util.UnitUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link Upgrader} contains the implementation of the upgrade methods
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class Upgrader {
public static final String ITEM_COPY_UNIT_TO_METADATA = "itemCopyUnitToMetadata";
public static final String LINK_UPGRADE_JS_PROFILE = "linkUpgradeJsProfile";
private final Logger logger = LoggerFactory.getLogger(Upgrader.class);
private final String baseDir;
private final boolean force;
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) {
UpgradeRecord upgradeRecord = upgradeRecords.get(key);
if (upgradeRecord != null && !force) {
logger.info("Already executed '{}' on {}. Use '--force' to execute it again.", key,
upgradeRecord.executionDate);
return false;
}
return true;
}
public void itemCopyUnitToMetadata() {
if (checkUpgradeRecord(ITEM_COPY_UNIT_TO_METADATA)) {
return;
}
Path itemJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.items.Item.json");
Path metadataJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.items.Metadata.json");
logger.info("Copying item unit from state description to metadata in database '{}'", itemJsonDatabasePath);
if (!Files.isReadable(itemJsonDatabasePath)) {
logger.error("Cannot access item database '{}', check path and access rights.", itemJsonDatabasePath);
return;
}
if (!Files.isWritable(metadataJsonDatabasePath)) {
logger.error("Cannot access metadata database '{}', check path and access rights.",
metadataJsonDatabasePath);
return;
}
JsonStorage<ManagedItemProvider.PersistedItem> itemStorage = new JsonStorage<>(itemJsonDatabasePath.toFile(),
null, 5, 0, 0, List.of());
JsonStorage<Metadata> metadataStorage = new JsonStorage<>(metadataJsonDatabasePath.toFile(), null, 5, 0, 0,
List.of());
itemStorage.getKeys().forEach(itemName -> {
ManagedItemProvider.PersistedItem item = itemStorage.get(itemName);
if (item != null && item.itemType.startsWith("Number:")) {
if (metadataStorage.containsKey("unit" + ":" + itemName)) {
logger.debug("{}: already contains a 'unit' metadata, skipping it", itemName);
} else {
Metadata metadata = metadataStorage.get("stateDescription:" + itemName);
if (metadata == null) {
logger.debug("{}: Nothing to do, no state description found.", itemName);
} else {
String pattern = (String) metadata.getConfiguration().get("pattern");
if (pattern.contains(UnitUtils.UNIT_PLACEHOLDER)) {
logger.warn(
"{}: State description contains unit place-holder '%unit%', check if 'unit' metadata is needed!",
itemName);
} else {
Unit<?> stateDescriptionUnit = UnitUtils.parseUnit(pattern);
if (stateDescriptionUnit != null) {
String unit = stateDescriptionUnit.toString();
MetadataKey defaultUnitMetadataKey = new MetadataKey("unit", itemName);
Metadata defaultUnitMetadata = new Metadata(defaultUnitMetadataKey, unit, null);
metadataStorage.put(defaultUnitMetadataKey.toString(), defaultUnitMetadata);
logger.info("{}: Wrote 'unit={}' to metadata.", itemName, unit);
}
}
}
}
}
});
metadataStorage.flush();
upgradeRecords.put(ITEM_COPY_UNIT_TO_METADATA, new UpgradeRecord(ZonedDateTime.now()));
}
public void linkUpgradeJsProfile() {
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()));
}
private static class UpgradeRecord {
public final ZonedDateTime executionDate;
public UpgradeRecord(ZonedDateTime executionDate) {
this.executionDate = executionDate;
}
}
}