2 * Copyright (c) 2010-2022 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.persistence.jdbc.console;
15 import java.util.Arrays;
16 import java.util.Comparator;
17 import java.util.List;
18 import java.util.stream.Stream;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.knowm.yank.exceptions.YankSQLException;
23 import org.openhab.core.io.console.Console;
24 import org.openhab.core.io.console.ConsoleCommandCompleter;
25 import org.openhab.core.io.console.StringsCompleter;
26 import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
27 import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
28 import org.openhab.core.persistence.PersistenceService;
29 import org.openhab.core.persistence.PersistenceServiceRegistry;
30 import org.openhab.persistence.jdbc.ItemTableCheckEntry;
31 import org.openhab.persistence.jdbc.ItemTableCheckEntryStatus;
32 import org.openhab.persistence.jdbc.internal.JdbcPersistenceService;
33 import org.openhab.persistence.jdbc.internal.JdbcPersistenceServiceConstants;
34 import org.osgi.service.component.annotations.Activate;
35 import org.osgi.service.component.annotations.Component;
36 import org.osgi.service.component.annotations.Reference;
39 * The {@link JdbcCommandExtension} is responsible for handling console commands
41 * @author Jacob Laursen - Initial contribution
44 @Component(service = ConsoleCommandExtension.class)
45 public class JdbcCommandExtension extends AbstractConsoleCommandExtension implements ConsoleCommandCompleter {
47 private static final String CMD_TABLES = "tables";
48 private static final String SUBCMD_TABLES_LIST = "list";
49 private static final String SUBCMD_TABLES_CLEAN = "clean";
50 private static final String PARAMETER_ALL = "all";
51 private static final String PARAMETER_FORCE = "force";
52 private static final StringsCompleter CMD_COMPLETER = new StringsCompleter(List.of(CMD_TABLES), false);
53 private static final StringsCompleter SUBCMD_TABLES_COMPLETER = new StringsCompleter(
54 List.of(SUBCMD_TABLES_LIST, SUBCMD_TABLES_CLEAN), false);
56 private final PersistenceServiceRegistry persistenceServiceRegistry;
59 public JdbcCommandExtension(final @Reference PersistenceServiceRegistry persistenceServiceRegistry) {
60 super(JdbcPersistenceServiceConstants.SERVICE_ID, "Interact with the JDBC persistence service.");
61 this.persistenceServiceRegistry = persistenceServiceRegistry;
65 public void execute(String[] args, Console console) {
66 if (args.length < 2 || args.length > 4 || !CMD_TABLES.equals(args[0])) {
70 JdbcPersistenceService persistenceService = getPersistenceService();
71 if (persistenceService == null) {
75 if (!execute(persistenceService, args, console)) {
79 } catch (YankSQLException e) {
80 console.println(e.toString());
84 private @Nullable JdbcPersistenceService getPersistenceService() {
85 for (PersistenceService persistenceService : persistenceServiceRegistry.getAll()) {
86 if (persistenceService instanceof JdbcPersistenceService) {
87 return (JdbcPersistenceService) persistenceService;
93 private boolean execute(JdbcPersistenceService persistenceService, String[] args, Console console) {
94 if (SUBCMD_TABLES_LIST.equalsIgnoreCase(args[1])) {
95 listTables(persistenceService, console, args.length == 3 && PARAMETER_ALL.equalsIgnoreCase(args[2]));
97 } else if (SUBCMD_TABLES_CLEAN.equalsIgnoreCase(args[1])) {
98 if (args.length == 3) {
99 cleanupItem(persistenceService, console, args[2], false);
101 } else if (args.length == 4 && PARAMETER_FORCE.equalsIgnoreCase(args[3])) {
102 cleanupItem(persistenceService, console, args[2], true);
105 cleanupTables(persistenceService, console);
112 private void listTables(JdbcPersistenceService persistenceService, Console console, Boolean all) {
113 List<ItemTableCheckEntry> entries = persistenceService.getCheckedEntries();
115 entries.removeIf(t -> t.getStatus() == ItemTableCheckEntryStatus.VALID);
117 entries.sort(Comparator.comparing(ItemTableCheckEntry::getTableName));
118 // FIXME: NoSuchElement when empty table - because of get()
119 int itemNameMaxLength = Math
120 .max(entries.stream().map(t -> t.getItemName().length()).max(Integer::compare).get(), 4);
121 int tableNameMaxLength = Math
122 .max(entries.stream().map(t -> t.getTableName().length()).max(Integer::compare).get(), 5);
123 int statusMaxLength = Stream.of(ItemTableCheckEntryStatus.values()).map(t -> t.toString().length())
124 .max(Integer::compare).get();
125 console.println(String.format(
126 "%1$-" + (tableNameMaxLength + 2) + "sRow Count %2$-" + (itemNameMaxLength + 2) + "s%3$s", "Table",
128 console.println("-".repeat(tableNameMaxLength) + " " + "--------- " + "-".repeat(itemNameMaxLength) + " "
129 + "-".repeat(statusMaxLength));
130 for (ItemTableCheckEntry entry : entries) {
131 String tableName = entry.getTableName();
132 ItemTableCheckEntryStatus status = entry.getStatus();
133 long rowCount = status == ItemTableCheckEntryStatus.VALID
134 || status == ItemTableCheckEntryStatus.ITEM_MISSING ? persistenceService.getRowCount(tableName) : 0;
135 console.println(String.format(
136 "%1$-" + (tableNameMaxLength + 2) + "s%2$9d %3$-" + (itemNameMaxLength + 2) + "s%4$s", tableName,
137 rowCount, entry.getItemName(), status));
141 private void cleanupTables(JdbcPersistenceService persistenceService, Console console) {
142 console.println("Cleaning up all inconsistent items...");
143 List<ItemTableCheckEntry> entries = persistenceService.getCheckedEntries();
144 entries.removeIf(t -> t.getStatus() == ItemTableCheckEntryStatus.VALID || t.getItemName().isEmpty());
145 for (ItemTableCheckEntry entry : entries) {
146 console.print(entry.getItemName() + " -> ");
147 if (persistenceService.cleanupItem(entry)) {
148 console.println("done.");
150 console.println("skipped/failed.");
155 private void cleanupItem(JdbcPersistenceService persistenceService, Console console, String itemName,
157 console.print("Cleaning up item " + itemName + "... ");
158 if (persistenceService.cleanupItem(itemName, force)) {
159 console.println("done.");
161 console.println("skipped/failed.");
166 public List<String> getUsages() {
167 return Arrays.asList(
168 buildCommandUsage(CMD_TABLES + " " + SUBCMD_TABLES_LIST + " [" + PARAMETER_ALL + "]",
169 "list tables (all = include valid)"),
171 CMD_TABLES + " " + SUBCMD_TABLES_CLEAN + " [<itemName>]" + " [" + PARAMETER_FORCE + "]",
172 "clean inconsistent items (remove from index and drop tables)"));
176 public @Nullable ConsoleCommandCompleter getCompleter() {
181 public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
182 if (cursorArgumentIndex <= 0) {
183 return CMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
184 } else if (cursorArgumentIndex == 1) {
185 if (CMD_TABLES.equalsIgnoreCase(args[0])) {
186 return SUBCMD_TABLES_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
188 } else if (cursorArgumentIndex == 2) {
189 if (CMD_TABLES.equalsIgnoreCase(args[0])) {
190 if (SUBCMD_TABLES_CLEAN.equalsIgnoreCase(args[1])) {
191 JdbcPersistenceService persistenceService = getPersistenceService();
192 if (persistenceService != null) {
193 return new StringsCompleter(persistenceService.getItemNames(), true).complete(args,
194 cursorArgumentIndex, cursorPosition, candidates);
196 } else if (SUBCMD_TABLES_LIST.equalsIgnoreCase(args[1])) {
197 new StringsCompleter(List.of(PARAMETER_ALL), false).complete(args, cursorArgumentIndex,
198 cursorPosition, candidates);