1   package com.atlassian.user.configuration.xml;
2   
3   import org.dom4j.*;
4   import org.dom4j.io.SAXReader;
5   import org.apache.log4j.Logger;
6   
7   import java.util.*;
8   import java.io.InputStream;
9   import java.io.IOException;
10  
11  import com.atlassian.user.configuration.*;
12  import com.atlassian.user.repository.RepositoryIdentifier;
13  import com.atlassian.user.repository.DefaultRepositoryIdentifier;
14  
15  /**
16   * Parses repository definitions and configuration out of an <code>atlassian-user.xml</code> file, with fall-back
17   * to the defaults loaded by the <code>XMLDefaultsParser</code>.
18   *
19   * <p>TODO: Document the file format.
20   *
21   * @see XMLDefaultsParser
22   */
23  public class XMLConfigurationParser
24  {
25      private final static Logger log = Logger.getLogger(XMLConfigurationParser.class);
26  
27      private final XMLDefaultsParser defaultsParser;
28  
29      /**
30       * List of ({@link RepositoryIdentifier}s in the order of delegation.
31       */
32      private List<RepositoryIdentifier> repositoryIdentifiers = new ArrayList<RepositoryIdentifier>();
33  
34      private Map<RepositoryIdentifier, RepositoryConfiguration> repositoryConfigurations = new HashMap<RepositoryIdentifier, RepositoryConfiguration>();
35  
36  
37      public XMLConfigurationParser() throws ConfigurationException
38      {
39          this(XMLDefaultsParser.DEFAULTS_FILE_NAME);
40      }
41  
42      public XMLConfigurationParser(String defaultsFileName) throws ConfigurationException
43      {
44          try
45          {
46              defaultsParser = new XMLDefaultsParser(defaultsFileName);
47          }
48          catch (RuntimeException e)
49          {
50              throw e;
51          }
52          catch (Exception e)
53          {
54              throw new ConfigurationException("Unable to load atlassian-user configuration parser: " + e.getMessage(), e);
55          }
56      }
57  
58      public void parse(InputStream docIS) throws ConfigurationException
59      {
60          try
61          {
62              if (docIS == null)
63                  throw new ConfigurationException("Null inputstream: cannot locate atlassian-user.xml");
64  
65              SAXReader reader = new SAXReader();
66              Document doc;
67              try
68              {
69                  doc = reader.read(docIS);
70              }
71              catch (DocumentException e)
72              {
73                  throw new ConfigurationException(e);
74              }
75  
76              Node delegationNode = doc.selectSingleNode("//" + Configuration.DELEGATION);
77              Node repositoriesNode = doc.selectSingleNode("//" + Configuration.REPOSITORIES);
78  
79              parseRepositories(repositoriesNode);
80  
81              if (delegationNode != null)
82                  parseDelegation(delegationNode);
83          }
84          catch (RuntimeException e)
85          {
86              throw e;
87          }
88          catch (Exception e)
89          {
90              throw new ConfigurationException("Unable to load atlassian-user configuration: " + e.getMessage(), e);
91          }
92      }
93  
94      protected void parseRepositories(Node repositoriesNode) throws ConfigurationException, DocumentException, IOException
95      {
96          List repositoryElements = repositoriesNode.selectNodes("*");
97  
98          if (repositoryElements.isEmpty())
99              throw new ConfigurationException("Nothing to init. There are no repositories specified.");
100 
101         for (Object repositoryElement1 : repositoryElements)
102         {
103             Element repositoryElement = (Element) repositoryElement1;
104 
105             // Elements in atlassian-user.xml have the name of their type, for example 'ldap', 'osuser' and so on.
106             String repositoryType = repositoryElement.getName();
107 
108             Map<String, String> defaultComponentClassNames = defaultsParser.getDefaultClassesConfigForKey(repositoryType);
109             Map<String, String> defaultComponents = defaultsParser.getDefaultParameterConfigForKey(repositoryType);
110 
111             //now we allow the config. to override the defaults
112             RepositoryIdentifier identifier = parseRepositoryIdentifier(repositoryElement);
113             if (repositoryIdentifiers.contains(identifier))
114             {
115                 throw new ConfigurationException("Repository keys must be unique. Please check that you have not " +
116                         "used the key '" + identifier.getKey() + "' more than once in your atlassian-user.xml file.");
117             }
118 
119             Map<String, String> componentClassNames = XMLConfigUtil.parseRepositoryElementForClassNames(repositoryElement);
120             Map<String, String> components = XMLConfigUtil.parseRepositoryElementForStringData(repositoryElement);
121 
122             /**
123              * Override the default class names (from atlassian-user-defaults.xml) with any values found in
124              * the config. XML file.
125              */
126             for (Object o : defaultComponentClassNames.keySet())
127             {
128                 String componentClassName = (String) o;
129                 if (!componentClassNames.containsKey(componentClassName))
130                 {
131                     componentClassNames.put(componentClassName, defaultComponentClassNames.get(componentClassName));
132                 }
133             }
134 
135             for (String componentName : defaultComponents.keySet())
136             {
137                 if (!components.containsKey(componentName))
138                 {
139                     components.put(componentName, defaultComponents.get(componentName));
140                 }
141             }
142 
143             RepositoryProcessor processor = instantiateProcessor(componentClassNames);
144 
145             RepositoryConfiguration configuration =
146                     new DefaultRepositoryConfiguration(identifier, processor, components, componentClassNames);
147 
148             if (isCachingEnabled(repositoryElement))
149                 configuration.setCacheConfiguration(parseCacheConfiguration());
150 
151             repositoryIdentifiers.add(identifier);
152             repositoryConfigurations.put(identifier, configuration);
153         }
154     }
155 
156     private RepositoryIdentifier parseRepositoryIdentifier(Element repositoryElement)
157     {
158         String key = repositoryElement.attributeValue("key");
159         if (key == null) throw new RuntimeException("Cannot specify repository without a key");
160         String name = repositoryElement.attributeValue("name", "Unnamed repository");
161         return new DefaultRepositoryIdentifier(key, name);
162     }
163 
164     private CacheConfiguration parseCacheConfiguration() throws DocumentException, IOException
165     {
166         Map classNames = defaultsParser.getDefaultClassesConfigForKey(Configuration.CACHE);
167         return new DefaultCacheConfiguration(classNames);
168     }
169 
170     private boolean isCachingEnabled(Element repositoryElement)
171     {
172         String cache = repositoryElement.attributeValue(Configuration.CACHE);
173         return cache != null && cache.equalsIgnoreCase("true");
174     }
175 
176     /**
177      * Reorders {@link #repositoryIdentifiers} according to the &lt;delegation&gt; element in atlassian-user.xml.
178      */
179     private void parseDelegation(Node delegationNode)
180     {
181         List<RepositoryIdentifier> delegationOrder = new LinkedList<RepositoryIdentifier>();
182 
183         for (Object o : delegationNode.selectNodes(Configuration.KEY))
184         {
185             String delegationKey = ((Element) o).getText();
186             for (RepositoryIdentifier identifier : repositoryIdentifiers)
187             {
188                 if (delegationKey.equals(identifier.getKey()))
189                 {
190                     delegationOrder.add(identifier);
191                     break;
192                 }
193             }
194         }
195         repositoryIdentifiers = delegationOrder;
196     }
197 
198     private RepositoryProcessor instantiateProcessor(Map processorInfo)
199     {
200         String processorClassName = (String) processorInfo.get(Configuration.PROCESSOR);
201         RepositoryProcessor processor = null;
202 
203         try
204         {
205             processor = (RepositoryProcessor) Class.forName(processorClassName).newInstance();
206         }
207         catch (Exception e)
208         {
209             log.error("Could not instantiate processor: " + e.getMessage());
210         }
211 
212         return processor;
213     }
214 
215     /**
216      * @return an unmodifiable list of {@link RepositoryConfiguration}s in order of delegation.
217      */
218     public List<RepositoryConfiguration> getRepositoryConfigurations()
219     {
220         List<RepositoryConfiguration> result = new LinkedList<RepositoryConfiguration>();
221 
222         for (RepositoryIdentifier identifier : repositoryIdentifiers)
223             result.add(repositoryConfigurations.get(identifier));
224         
225         return Collections.unmodifiableList(result);
226     }
227 }