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