]> git.basschouten.com Git - openhab-addons.git/blob
4d4c6213af33c1aefc35c9b89d3fa268646fe25d
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.persistence.jdbc.internal.console;
14
15 import java.util.Arrays;
16 import java.util.Collection;
17 import java.util.Comparator;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Map.Entry;
21 import java.util.stream.Collectors;
22 import java.util.stream.Stream;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.core.io.console.Console;
27 import org.openhab.core.io.console.ConsoleCommandCompleter;
28 import org.openhab.core.io.console.StringsCompleter;
29 import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
30 import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
31 import org.openhab.core.persistence.PersistenceService;
32 import org.openhab.core.persistence.PersistenceServiceRegistry;
33 import org.openhab.persistence.jdbc.internal.ItemTableCheckEntry;
34 import org.openhab.persistence.jdbc.internal.ItemTableCheckEntryStatus;
35 import org.openhab.persistence.jdbc.internal.JdbcPersistenceService;
36 import org.openhab.persistence.jdbc.internal.JdbcPersistenceServiceConstants;
37 import org.openhab.persistence.jdbc.internal.exceptions.JdbcSQLException;
38 import org.osgi.service.component.annotations.Activate;
39 import org.osgi.service.component.annotations.Component;
40 import org.osgi.service.component.annotations.Reference;
41
42 /**
43  * The {@link JdbcCommandExtension} is responsible for handling console commands
44  *
45  * @author Jacob Laursen - Initial contribution
46  */
47 @NonNullByDefault
48 @Component(service = ConsoleCommandExtension.class)
49 public class JdbcCommandExtension extends AbstractConsoleCommandExtension implements ConsoleCommandCompleter {
50
51     private static final String CMD_SCHEMA = "schema";
52     private static final String CMD_TABLES = "tables";
53     private static final String CMD_RELOAD = "reload";
54     private static final String SUBCMD_SCHEMA_CHECK = "check";
55     private static final String SUBCMD_SCHEMA_FIX = "fix";
56     private static final String SUBCMD_TABLES_LIST = "list";
57     private static final String SUBCMD_TABLES_CLEAN = "clean";
58     private static final String PARAMETER_ALL = "all";
59     private static final String PARAMETER_FORCE = "force";
60     private static final StringsCompleter CMD_COMPLETER = new StringsCompleter(
61             List.of(CMD_SCHEMA, CMD_TABLES, CMD_RELOAD), false);
62     private static final StringsCompleter SUBCMD_SCHEMA_COMPLETER = new StringsCompleter(
63             List.of(SUBCMD_SCHEMA_CHECK, SUBCMD_SCHEMA_FIX), false);
64     private static final StringsCompleter SUBCMD_TABLES_COMPLETER = new StringsCompleter(
65             List.of(SUBCMD_TABLES_LIST, SUBCMD_TABLES_CLEAN), false);
66
67     private final PersistenceServiceRegistry persistenceServiceRegistry;
68
69     @Activate
70     public JdbcCommandExtension(final @Reference PersistenceServiceRegistry persistenceServiceRegistry) {
71         super(JdbcPersistenceServiceConstants.SERVICE_ID, "Interact with the JDBC persistence service.");
72         this.persistenceServiceRegistry = persistenceServiceRegistry;
73     }
74
75     @Override
76     public void execute(String[] args, Console console) {
77         if (args.length < 1 || args.length > 4) {
78             printUsage(console);
79             return;
80         }
81         JdbcPersistenceService persistenceService = getPersistenceService();
82         if (persistenceService == null) {
83             return;
84         }
85         try {
86             if (!execute(persistenceService, args, console)) {
87                 printUsage(console);
88                 return;
89             }
90         } catch (JdbcSQLException e) {
91             console.println(e.toString());
92         }
93     }
94
95     private @Nullable JdbcPersistenceService getPersistenceService() {
96         for (PersistenceService persistenceService : persistenceServiceRegistry.getAll()) {
97             if (persistenceService instanceof JdbcPersistenceService service) {
98                 return service;
99             }
100         }
101         return null;
102     }
103
104     private boolean execute(JdbcPersistenceService persistenceService, String[] args, Console console)
105             throws JdbcSQLException {
106         if (args.length > 1 && CMD_TABLES.equalsIgnoreCase(args[0])) {
107             if (SUBCMD_TABLES_LIST.equalsIgnoreCase(args[1])) {
108                 listTables(persistenceService, console, args.length == 3 && PARAMETER_ALL.equalsIgnoreCase(args[2]));
109                 return true;
110             } else if (SUBCMD_TABLES_CLEAN.equalsIgnoreCase(args[1])) {
111                 if (args.length == 3) {
112                     cleanupItem(persistenceService, console, args[2], false);
113                     return true;
114                 } else if (args.length == 4 && PARAMETER_FORCE.equalsIgnoreCase(args[3])) {
115                     cleanupItem(persistenceService, console, args[2], true);
116                     return true;
117                 } else {
118                     cleanupTables(persistenceService, console);
119                     return true;
120                 }
121             }
122         } else if (args.length > 1 && CMD_SCHEMA.equalsIgnoreCase(args[0])) {
123             if (args.length == 2 && SUBCMD_SCHEMA_CHECK.equalsIgnoreCase(args[1])) {
124                 checkSchema(persistenceService, console);
125                 return true;
126             } else if (SUBCMD_SCHEMA_FIX.equalsIgnoreCase(args[1])) {
127                 if (args.length == 2) {
128                     fixSchema(persistenceService, console);
129                     return true;
130                 } else if (args.length == 3) {
131                     fixSchema(persistenceService, console, args[2]);
132                     return true;
133                 }
134             }
135         } else if (args.length == 1 && CMD_RELOAD.equalsIgnoreCase(args[0])) {
136             reload(persistenceService, console);
137             return true;
138         }
139         return false;
140     }
141
142     private void checkSchema(JdbcPersistenceService persistenceService, Console console) throws JdbcSQLException {
143         List<Entry<String, String>> itemNameToTableName = persistenceService.getItemNameToTableNameMap().entrySet()
144                 .stream().sorted(Map.Entry.comparingByKey()).collect(Collectors.toList());
145         int itemNameMaxLength = Math
146                 .max(itemNameToTableName.stream().map(i -> i.getKey().length()).max(Integer::compare).orElse(0), 4);
147         int tableNameMaxLength = Math
148                 .max(itemNameToTableName.stream().map(i -> i.getValue().length()).max(Integer::compare).orElse(0), 5);
149         console.println(String.format("%1$-" + (tableNameMaxLength + 2) + "s%2$-" + (itemNameMaxLength + 2) + "s%3$s",
150                 "Table", "Item", "Issue"));
151         console.println("-".repeat(tableNameMaxLength) + "  " + "-".repeat(itemNameMaxLength) + "  " + "-".repeat(64));
152         for (Entry<String, String> entry : itemNameToTableName) {
153             String itemName = entry.getKey();
154             String tableName = entry.getValue();
155             Collection<String> issues = persistenceService.getSchemaIssues(tableName, itemName);
156             if (!issues.isEmpty()) {
157                 for (String issue : issues) {
158                     console.println(String.format(
159                             "%1$-" + (tableNameMaxLength + 2) + "s%2$-" + (itemNameMaxLength + 2) + "s%3$s", tableName,
160                             itemName, issue));
161                 }
162             }
163         }
164     }
165
166     private void fixSchema(JdbcPersistenceService persistenceService, Console console) {
167         List<Entry<String, String>> itemNameToTableName = persistenceService.getItemNameToTableNameMap().entrySet()
168                 .stream().sorted(Map.Entry.comparingByKey()).collect(Collectors.toList());
169         for (Entry<String, String> entry : itemNameToTableName) {
170             String itemName = entry.getKey();
171             String tableName = entry.getValue();
172             fixSchema(persistenceService, console, tableName, itemName);
173         }
174     }
175
176     private void fixSchema(JdbcPersistenceService persistenceService, Console console, String itemName) {
177         Map<String, String> itemNameToTableNameMap = persistenceService.getItemNameToTableNameMap();
178         String tableName = itemNameToTableNameMap.get(itemName);
179         if (tableName != null) {
180             fixSchema(persistenceService, console, tableName, itemName);
181         } else {
182             console.println("Table not found for item '" + itemName + "'");
183         }
184     }
185
186     private void fixSchema(JdbcPersistenceService persistenceService, Console console, String tableName,
187             String itemName) {
188         try {
189             if (persistenceService.fixSchemaIssues(tableName, itemName)) {
190                 console.println("Fixed table '" + tableName + "' for item '" + itemName + "'");
191             }
192         } catch (JdbcSQLException e) {
193             console.println("Failed to fix table '" + tableName + "' for item '" + itemName + "': " + e.getMessage());
194         }
195     }
196
197     private void listTables(JdbcPersistenceService persistenceService, Console console, boolean all)
198             throws JdbcSQLException {
199         List<ItemTableCheckEntry> entries = persistenceService.getCheckedEntries();
200         if (!all) {
201             entries.removeIf(t -> t.getStatus() == ItemTableCheckEntryStatus.VALID);
202         }
203         entries.sort(Comparator.comparing(ItemTableCheckEntry::getTableName));
204         int itemNameMaxLength = Math
205                 .max(entries.stream().map(t -> t.getItemName().length()).max(Integer::compare).orElse(0), 4);
206         int tableNameMaxLength = Math
207                 .max(entries.stream().map(t -> t.getTableName().length()).max(Integer::compare).orElse(0), 5);
208         int statusMaxLength = Stream.of(ItemTableCheckEntryStatus.values()).map(t -> t.toString().length())
209                 .max(Integer::compare).get();
210         console.println(String.format(
211                 "%1$-" + (tableNameMaxLength + 2) + "sRow Count  %2$-" + (itemNameMaxLength + 2) + "s%3$s", "Table",
212                 "Item", "Status"));
213         console.println("-".repeat(tableNameMaxLength) + "  " + "---------  " + "-".repeat(itemNameMaxLength) + "  "
214                 + "-".repeat(statusMaxLength));
215         for (ItemTableCheckEntry entry : entries) {
216             String tableName = entry.getTableName();
217             ItemTableCheckEntryStatus status = entry.getStatus();
218             long rowCount = status == ItemTableCheckEntryStatus.VALID
219                     || status == ItemTableCheckEntryStatus.ITEM_MISSING ? persistenceService.getRowCount(tableName) : 0;
220             console.println(String.format(
221                     "%1$-" + (tableNameMaxLength + 2) + "s%2$9d  %3$-" + (itemNameMaxLength + 2) + "s%4$s", tableName,
222                     rowCount, entry.getItemName(), status));
223         }
224     }
225
226     private void cleanupTables(JdbcPersistenceService persistenceService, Console console) throws JdbcSQLException {
227         console.println("Cleaning up all inconsistent items...");
228         List<ItemTableCheckEntry> entries = persistenceService.getCheckedEntries();
229         entries.removeIf(t -> t.getStatus() == ItemTableCheckEntryStatus.VALID || t.getItemName().isEmpty());
230         for (ItemTableCheckEntry entry : entries) {
231             console.print(entry.getItemName() + " -> ");
232             if (persistenceService.cleanupItem(entry)) {
233                 console.println("done.");
234             } else {
235                 console.println("skipped/failed.");
236             }
237         }
238     }
239
240     private void cleanupItem(JdbcPersistenceService persistenceService, Console console, String itemName, boolean force)
241             throws JdbcSQLException {
242         console.print("Cleaning up item " + itemName + "... ");
243         if (persistenceService.cleanupItem(itemName, force)) {
244             console.println("done.");
245         } else {
246             console.println("skipped/failed.");
247         }
248     }
249
250     private void reload(JdbcPersistenceService persistenceService, Console console) throws JdbcSQLException {
251         persistenceService.populateItemNameToTableNameMap();
252         console.println("Item index reloaded.");
253     }
254
255     @Override
256     public List<String> getUsages() {
257         return Arrays.asList(buildCommandUsage(CMD_SCHEMA + " " + SUBCMD_SCHEMA_CHECK, "check schema integrity"),
258                 buildCommandUsage(CMD_SCHEMA + " " + SUBCMD_SCHEMA_FIX + " [<itemName>]", "fix schema integrity"),
259                 buildCommandUsage(CMD_TABLES + " " + SUBCMD_TABLES_LIST + " [" + PARAMETER_ALL + "]",
260                         "list tables (all = include valid)"),
261                 buildCommandUsage(
262                         CMD_TABLES + " " + SUBCMD_TABLES_CLEAN + " [<itemName>]" + " [" + PARAMETER_FORCE + "]",
263                         "clean inconsistent items (remove from index and drop tables)"),
264                 buildCommandUsage(CMD_RELOAD, "reload item index/schema"));
265     }
266
267     @Override
268     public @Nullable ConsoleCommandCompleter getCompleter() {
269         return this;
270     }
271
272     @Override
273     public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
274         if (cursorArgumentIndex <= 0) {
275             return CMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
276         } else if (cursorArgumentIndex == 1) {
277             if (CMD_TABLES.equalsIgnoreCase(args[0])) {
278                 return SUBCMD_TABLES_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
279             } else if (CMD_SCHEMA.equalsIgnoreCase(args[0])) {
280                 return SUBCMD_SCHEMA_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
281             }
282         } else if (cursorArgumentIndex == 2) {
283             if (CMD_TABLES.equalsIgnoreCase(args[0])) {
284                 if (SUBCMD_TABLES_CLEAN.equalsIgnoreCase(args[1])) {
285                     JdbcPersistenceService persistenceService = getPersistenceService();
286                     if (persistenceService != null) {
287                         return new StringsCompleter(persistenceService.getItemNames(), true).complete(args,
288                                 cursorArgumentIndex, cursorPosition, candidates);
289                     }
290                 } else if (SUBCMD_TABLES_LIST.equalsIgnoreCase(args[1])) {
291                     new StringsCompleter(List.of(PARAMETER_ALL), false).complete(args, cursorArgumentIndex,
292                             cursorPosition, candidates);
293                 }
294             } else if (CMD_SCHEMA.equalsIgnoreCase(args[0])) {
295                 if (SUBCMD_SCHEMA_FIX.equalsIgnoreCase(args[1])) {
296                     JdbcPersistenceService persistenceService = getPersistenceService();
297                     if (persistenceService != null) {
298                         return new StringsCompleter(persistenceService.getItemNames(), true).complete(args,
299                                 cursorArgumentIndex, cursorPosition, candidates);
300                     }
301                 }
302             }
303         }
304         return false;
305     }
306 }