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}