001package jmri.util.prefs; 002 003import java.io.File; 004import java.io.FileInputStream; 005import java.io.FileOutputStream; 006import java.io.IOException; 007import java.io.InputStream; 008import java.io.OutputStream; 009import java.nio.charset.StandardCharsets; 010import jmri.profile.AuxiliaryConfiguration; 011import jmri.util.FileUtil; 012import jmri.util.ThreadingUtil; 013import jmri.util.xml.XMLUtil; 014import org.slf4j.Logger; 015import org.slf4j.LoggerFactory; 016import org.w3c.dom.DOMException; 017import org.w3c.dom.Document; 018import org.w3c.dom.Element; 019import org.w3c.dom.Node; 020import org.w3c.dom.NodeList; 021import org.xml.sax.InputSource; 022import org.xml.sax.SAXException; 023 024/** 025 * 026 * @author Randall Wood 027 */ 028public abstract class JmriConfiguration implements AuxiliaryConfiguration { 029 030 private static final Logger log = LoggerFactory.getLogger(JmriConfiguration.class); 031 032 JmriConfiguration() { 033 } 034 035 protected abstract File getConfigurationFile(boolean shared); 036 037 protected abstract boolean isSharedBackedUp(); 038 039 protected abstract void setSharedBackedUp(boolean backedUp); 040 041 protected abstract boolean isPrivateBackedUp(); 042 043 protected abstract void setPrivateBackedUp(boolean backedUp); 044 045 Document persistentDocument; 046 047 048 Document getDocumentFromFile(final boolean shared) { 049 File file = this.getConfigurationFile(shared); 050 if (file != null && file.canRead()) { 051 try { 052 try (final InputStream is = new FileInputStream(file)) { 053 InputSource input = new InputSource(is); 054 input.setSystemId(file.toURI().toURL().toString()); 055 056 log.debug("Start read of user-interface.xml"); 057 058 var document = XMLUtil.parse(input, false, true, null, null); 059 log.debug("End read of user-interface.xml"); 060 return document; 061 } 062 } catch (IOException | SAXException | IllegalArgumentException ex) { 063 log.warn("Cannot parse {}", file, ex); 064 return null; 065 } 066 } 067 return null; 068 } 069 070 @Override 071 public Element getConfigurationFragment(final String elementName, final String namespace, final boolean shared) { 072 return ThreadingUtil.runOnGUIwithReturn(() -> { 073 synchronized (this) { 074 if (persistentDocument == null) { 075 persistentDocument = getDocumentFromFile(shared); 076 } 077 if (persistentDocument == null) { 078 return null; 079 } 080 Element root = persistentDocument.getDocumentElement(); 081 return XMLUtil.findElement(root, elementName, namespace); 082 } 083 }); 084 } 085 086 @Override 087 public void putConfigurationFragment(final Element fragment, final boolean shared) throws IllegalArgumentException { 088 ThreadingUtil.runOnGUI(() -> { 089 synchronized (this) { 090 String elementName = fragment.getLocalName(); 091 String namespace = fragment.getNamespaceURI(); 092 if (namespace == null) { 093 throw new IllegalArgumentException(); 094 } 095 if (persistentDocument == null) { 096 persistentDocument = getDocumentFromFile(shared); 097 } 098 if (persistentDocument == null) { 099 persistentDocument = XMLUtil.createDocument("auxiliary-configuration", JmriConfigurationProvider.NAMESPACE, null, null); // NOI18N 100 } 101 Element root = persistentDocument.getDocumentElement(); 102 Element oldFragment = XMLUtil.findElement(root, elementName, namespace); 103 if (oldFragment != null) { 104 root.removeChild(oldFragment); 105 } 106 Node ref = null; 107 NodeList list = root.getChildNodes(); 108 for (int i = 0; i < list.getLength(); i++) { 109 Node node = list.item(i); 110 if (node.getNodeType() != Node.ELEMENT_NODE) { 111 continue; 112 } 113 int comparison = node.getNodeName().compareTo(elementName); 114 if (comparison == 0) { 115 comparison = node.getNamespaceURI().compareTo(namespace); 116 } 117 if (comparison > 0) { 118 ref = node; 119 break; 120 } 121 } 122 root.insertBefore(root.getOwnerDocument().importNode(fragment, true), ref); 123 File file = this.getConfigurationFile(shared); 124 try { 125 this.backup(shared); 126 try (final OutputStream os = new FileOutputStream(file)) { 127 log.debug("Start write of user-interface.xml"); 128 try { 129 XMLUtil.write(persistentDocument, os, StandardCharsets.UTF_8.name()); 130 } catch (IOException ex) { 131 log.error("Cannot write {}", file, ex); 132 } 133 log.debug("End write of user-interface.xml"); 134 os.flush(); 135 } 136 } catch (IOException ex) { 137 log.error("Cannot write {}", file, ex); 138 } 139 } 140 }); 141 } 142 143 @Override 144 public boolean removeConfigurationFragment(final String elementName, final String namespace, final boolean shared) throws IllegalArgumentException { 145 return ThreadingUtil.runOnGUIwithReturn(() -> { 146 synchronized (this) { 147 File file = this.getConfigurationFile(shared); 148 if (file.canWrite()) { 149 try { 150 if (persistentDocument == null) { 151 persistentDocument = getDocumentFromFile(shared); 152 } 153 Element root = persistentDocument.getDocumentElement(); 154 Element toRemove = XMLUtil.findElement(root, elementName, namespace); 155 if (toRemove != null) { 156 root.removeChild(toRemove); 157 this.backup(shared); 158 if (root.getElementsByTagName("*").getLength() > 0) { 159 // NOI18N 160 try (final OutputStream os = new FileOutputStream(file)) { 161 log.debug("Start write of user-interface.xml"); 162 XMLUtil.write(persistentDocument, os, StandardCharsets.UTF_8.name()); 163 log.debug("End write of user-interface.xml"); 164 } 165 } else if (!file.delete()) { 166 log.warn("Unable to delete {}", file); 167 } 168 return true; 169 } 170 } catch (IOException | DOMException ex) { 171 log.error("Cannot remove {} from {}", elementName, file, ex); 172 } 173 } 174 return false; 175 } 176 }); 177 } 178 179 private void backup(boolean shared) { 180 final File file = this.getConfigurationFile(shared); 181 if (!(shared ? this.isSharedBackedUp() : this.isPrivateBackedUp()) && file.exists()) { 182 log.debug("Backing up {}", file); 183 try { 184 FileUtil.backup(file); 185 if (shared) { 186 this.setSharedBackedUp(true); 187 } else { 188 this.setPrivateBackedUp(true); 189 } 190 } catch (IOException ex) { 191 log.error("Error backing up {}", file, ex); 192 } 193 } 194 } 195 196}