1   package com.atlassian.config.xml;
2   
3   import com.atlassian.config.AbstractConfigurationPersister;
4   import com.atlassian.config.ConfigurationException;
5   import com.atlassian.core.util.Dom4jUtil;
6   import org.dom4j.Document;
7   import org.dom4j.DocumentException;
8   import org.dom4j.DocumentHelper;
9   import org.dom4j.Element;
10  import org.dom4j.io.SAXReader;
11  import org.slf4j.Logger;
12  import org.slf4j.LoggerFactory;
13  import org.xml.sax.SAXException;
14  
15  import javax.xml.XMLConstants;
16  import java.io.File;
17  import java.io.IOException;
18  import java.io.InputStream;
19  import java.net.MalformedURLException;
20  import java.util.List;
21  import java.util.Map;
22  
23  public abstract class AbstractDom4jXmlConfigurationPersister extends AbstractConfigurationPersister
24  {
25      /** @deprecated since 0.16. Create a private SLF4J Logger instead. */
26      @Deprecated
27      public static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(AbstractDom4jXmlConfigurationPersister.class);
28      private static final Logger privateLog = LoggerFactory.getLogger(AbstractDom4jXmlConfigurationPersister.class);
29      private Document document;
30      protected boolean useCData = false;
31  
32      public AbstractDom4jXmlConfigurationPersister()
33      {
34          //Create the document root
35          clearDocument();
36  
37          //These define the mappings for the different objects stored in the config file
38          addConfigMapping(String.class, Dom4jXmlStringConfigElement.class);
39          addConfigMapping(Map.class, Dom4jXmlMapConfigElement.class);
40          addConfigMapping(Map.Entry.class, Dom4jXmlMapEntryConfigElement.class);
41          addConfigMapping(List.class, Dom4jXmlListConfigElement.class);
42      }
43  
44      public Document loadDocument(File xmlFile) throws DocumentException, MalformedURLException
45      {
46          SAXReader xmlReader = newSecureSaxReader();
47          document = xmlReader.read(xmlFile);
48          return document;
49      }
50  
51      public Object load(InputStream istream) throws ConfigurationException
52      {
53          try
54          {
55              return loadDocument(istream);
56          }
57          catch (DocumentException e)
58          {
59              throw new ConfigurationException("Failed to load Xml doc: " + e.getMessage(), e);
60          }
61      }
62  
63      public Document loadDocument(InputStream istream) throws DocumentException
64      {
65          SAXReader xmlReader = newSecureSaxReader();
66          document = xmlReader.read(istream);
67          return document;
68      }
69  
70      public void save(String configPath, String configFile) throws ConfigurationException
71      {
72          saveDocument(configPath, configFile);
73      }
74  
75      public void saveDocument(String configPath, String configFile) throws ConfigurationException
76      {
77          try
78          {
79              saveDocumentAtomically(getDocument(), configPath, configFile);
80          }
81          catch (IOException e)
82          {
83              throw new ConfigurationException("Couldn't save " + configFile + " to " + configPath + " directory.", e);
84          }
85      }
86  
87      private void saveDocumentAtomically(Document document, String configPath, String configFile) throws IOException
88      {
89          File tempFile = File.createTempFile(configFile, "tmp", new File(configPath));
90          File saveFile = new File(configPath, configFile);
91  
92          try
93          {
94              Dom4jUtil.saveDocumentTo(document, configPath, tempFile.getName());
95  
96              // If the temp directory is on a different filesystem to the destination, the rename may fail on some
97              // operating systems.
98              if (!tempFile.renameTo(saveFile))
99              {
100                 privateLog.warn("Unable to move " + tempFile.getCanonicalPath() + " to " + saveFile.getCanonicalPath() + ". Falling back to non-atomic overwrite.");
101                 Dom4jUtil.saveDocumentTo(document, configPath, configFile);
102             }
103         }
104         finally
105         {
106             tempFile.delete();
107         }
108     }
109 
110     public Document getDocument()
111     {
112         return document;
113     }
114 
115     public Object getRootContext()
116     {
117         return document.getRootElement();
118     }
119 
120     public Element getElement(String path)
121     {
122         return DocumentHelper.makeElement(document, path);
123     }
124 
125     public void clear()
126     {
127         clearDocument();
128     }
129 
130     private void clearDocument()
131     {
132         document = null;
133         document = DocumentHelper.createDocument();
134         document.addElement(getRootName());
135     }
136 
137     public abstract String getRootName();
138 
139     public boolean isUseCData()
140     {
141         return useCData;
142     }
143 
144     public void setUseCData(boolean useCData)
145     {
146         this.useCData = useCData;
147     }
148 
149     private SAXReader newSecureSaxReader()
150     {
151         SAXReader saxReader = new SAXReader();
152         setFeature(saxReader, "http://xml.org/sax/features/external-parameter-entities", false);
153         setFeature(saxReader, "http://xml.org/sax/features/external-general-entities", false);
154         setFeature(saxReader, "http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
155         setFeature(saxReader, XMLConstants.FEATURE_SECURE_PROCESSING, true);
156         return saxReader;
157     }
158 
159     private static void setFeature(final SAXReader xmlReader, final String name, final boolean value)
160     {
161         try
162         {
163             xmlReader.setFeature(name, value);
164         }
165         catch (SAXException e)
166         {
167             privateLog.warn("Unable to set " + name + " to " + value);
168         }
169     }
170 }