View Javadoc

1   /*****************************************************************
2    * Licensed to the Apache Software Foundation (ASF) under one   *
3    * or more contributor license agreements.  See the NOTICE file *
4    * distributed with this work for additional information        *
5    * regarding copyright ownership.  The ASF licenses this file   *
6    * to you under the Apache License, Version 2.0 (the            *
7    * "License"); you may not use this file except in compliance   *
8    * with the License.  You may obtain a copy of the License at   *
9    *                                                              *
10   *   http://www.apache.org/licenses/LICENSE-2.0                 *
11   *                                                              *
12   * Unless required by applicable law or agreed to in writing,   *
13   * software distributed under the License is distributed on an  *
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15   * KIND, either express or implied.  See the License for the    *
16   * specific language governing permissions and limitations      *
17   * under the License.                                           *
18   ****************************************************************/
19  
20  
21  package org.apache.james.util;
22  
23  import org.apache.oro.text.perl.MalformedPerl5PatternException;
24  import org.apache.oro.text.perl.Perl5Util;
25  import org.w3c.dom.Attr;
26  import org.w3c.dom.Document;
27  import org.w3c.dom.Element;
28  import org.w3c.dom.NamedNodeMap;
29  import org.w3c.dom.NodeList;
30  
31  import javax.xml.parsers.DocumentBuilder;
32  import javax.xml.parsers.DocumentBuilderFactory;
33  import java.io.File;
34  import java.util.HashMap;
35  import java.util.Iterator;
36  import java.util.Map;
37  
38  /*
39   * This is derived from the SQLResources.java class that we have been
40   * using to provide SQL strings based upon the particular drive being
41   * used.
42   *
43   * The format is indentical to the format used by SQLResources.  The
44   * only difference are the names of the elements and attributes.  A
45   * mapping is as follows:
46   *
47   *      sqlResources            xmlResources
48   *      ------------            ------------
49   *      sqlResources            resources
50   *      dbMatchers              matchers
51   *      dbMatcher               matcher
52   *      db                      for
53   *      databaseProductName     match
54   *      sqlDefs                 group
55   *      sql                     resource
56   *
57   * This class provides String resources defined in XML.  Resources are
58   * organized into groups, and identified by name.  For each resource
59   * there can be a standard value, and custom values matched by regular
60   * expression.
61   *
62   * The structure of the XML file is:
63   *
64   *  <resources>
65   *    <matchers>
66   *      &lt;matcher for="<i>label</i>" match="<i>regular expression</i>"/&gt;
67   *      ...
68   *    &lt;/matchers&gt;
69   *    &lt;group name="<i>group name</i>"&gt;
70   *      &lt;resource name="<i>resouce name</i>" [for="<i>match label</i>"]&gt;<i>text, including ${placeholders}, which will be replaced at runtime.</i>&lt;/resource&gt;
71   *      ...
72   *    &lt;/group&gt;
73   *    <i>... more &lt;group&gt; elements ...</i>
74   *  &lt;/resources&gt;
75   * 
76   */
77  public class XMLResources
78  {
79      /***
80       * A map of statement types to resource strings
81       */
82      private Map m_resource = new HashMap();
83  
84      /***
85       * A set of all used String values
86       */
87      static private Map stringTable = java.util.Collections.synchronizedMap(new HashMap());
88  
89      /***
90       * A Perl5 regexp matching helper class
91       */
92      private Perl5Util m_perl5Util = new Perl5Util();
93  
94      /***
95       * Configures an XMLResources object to provide string statements from a file.
96       * 
97       * Parameters encoded as $(parameter} in the input file are
98       * replace by values from the parameters Map, if the named parameter exists.
99       * Parameter values may also be specified in the resourceSection element.
100      * 
101      * @param xmlFile    the input file containing the string definitions
102      * @param group      xml element containing the strings to be used
103      * @param select     if customized elements exist for this value, use them instead of the default
104      * @param configParameters a map of parameters (name-value string pairs) which are
105      *                   replaced where found in the input strings
106      */
107     public void init(File xmlFile, String group,
108                      String select, Map configParameters)
109         throws Exception
110     {
111         // Parse the xmlFile as an XML document.
112         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
113         DocumentBuilder builder = factory.newDocumentBuilder();
114         Document doc = builder.parse(xmlFile);
115 
116         // First process the matchers, to select the statements to use.
117         Element matcherElement = 
118             (Element)(doc.getElementsByTagName("matchers").item(0));
119         String selectTag = null;
120         if ( matcherElement != null ) {
121             selectTag = match(select, matcherElement);
122             m_perl5Util = null;     // release the PERL matcher!
123         }
124 
125         // Now get the section defining strings for the group required.
126         NodeList sections = doc.getElementsByTagName("group");
127         int sectionsCount = sections.getLength();
128         Element sectionElement = null;
129         for (int i = 0; i < sectionsCount; i++ ) {
130             sectionElement = (Element)(sections.item(i));
131             String sectionName = sectionElement.getAttribute("name");
132             if ( sectionName != null && sectionName.equals(group) ) {
133                 break;
134             }
135 
136         }
137         if ( sectionElement == null ) {
138             StringBuffer exceptionBuffer =
139                 new StringBuffer(64)
140                         .append("Error loading string definition file. ")
141                         .append("The element named \'")
142                         .append(group)
143                         .append("\' does not exist.");
144             throw new RuntimeException(exceptionBuffer.toString());
145         }
146 
147         // Get parameters defined within the file as defaults,
148         // and use supplied parameters as overrides.
149         Map parameters = new HashMap();
150         // First read from the <params> element, if it exists.
151         Element parametersElement = 
152             (Element)(sectionElement.getElementsByTagName("parameters").item(0));
153         if ( parametersElement != null ) {
154             NamedNodeMap params = parametersElement.getAttributes();
155             int paramCount = params.getLength();
156             for (int i = 0; i < paramCount; i++ ) {
157                 Attr param = (Attr)params.item(i);
158                 String paramName = param.getName();
159                 String paramValue = param.getValue();
160                 parameters.put(paramName, paramValue);
161             }
162         }
163         // Then copy in the parameters supplied with the call.
164         parameters.putAll(configParameters);
165 
166         // 2 maps - one for storing default statements,
167         // the other for statements with a "for" attribute matching this 
168         // connection.
169         Map defaultStrings = new HashMap();
170         Map selectTagStrings = new HashMap();
171 
172         // Process each string resource, replacing string parameters,
173         // and adding to the appropriate map..
174         NodeList resDefs = sectionElement.getElementsByTagName("resource");
175         int resCount = resDefs.getLength();
176         for ( int i = 0; i < resCount; i++ ) {
177             // See if this needs to be processed (is default or product specific)
178             Element resElement = (Element)(resDefs.item(i));
179             String resSelect = resElement.getAttribute("for");
180             Map resMap;
181             if ( resSelect.equals("")) {
182                 // default
183                 resMap = defaultStrings;
184             }
185             else if (resSelect.equals(selectTag) ) {
186                 // Specific to this product
187                 resMap = selectTagStrings;
188             }
189             else {
190                 // for a different product
191                 continue;
192             }
193 
194             // Get the key and value for this string resource.
195             String resKey = resElement.getAttribute("name");
196             if ( resKey == null ) {
197                 // ignore elements without a "name" attribute.
198                 continue;
199             }
200             String resString = resElement.getFirstChild().getNodeValue();
201 
202             // Do parameter replacements for this string resource.
203             Iterator paramNames = parameters.keySet().iterator();
204             while ( paramNames.hasNext() ) {
205                 String paramName = (String)paramNames.next();
206                 String paramValue = (String)parameters.get(paramName);
207 
208                 StringBuffer replaceBuffer =
209                     new StringBuffer(64)
210                             .append("${")
211                             .append(paramName)
212                             .append("}");
213                 resString = substituteSubString(resString, replaceBuffer.toString(), paramValue);
214             }
215 
216             // See if we already have registered a string of this value
217             String shared = (String) stringTable.get(resString);
218             // If not, register it -- we will use it next time
219             if (shared == null) {
220                 stringTable.put(resString, resString);
221             } else {
222                 resString = shared;
223             }
224 
225             // Add to the resMap - either the "default" or the "product" map
226             resMap.put(resKey, resString);
227         }
228 
229         // Copy in default strings, then overwrite product-specific ones.
230         m_resource.putAll(defaultStrings);
231         m_resource.putAll(selectTagStrings);
232     }
233 
234     /***
235      * Compares the "select" value against a set of regular expressions
236      * defined in XML.  The first successful match defines the name of a
237      * selector tag. This value is then used to choose the specific
238      * expressions to use.
239      *
240      * @param select the String to be checked
241      * @param matchersElement the XML element containing selector patterns
242      *
243      * @return the selector tag that will be used to select custom resources
244      *
245      */
246     private String match(String select, Element matchersElement)
247         throws MalformedPerl5PatternException
248     {
249         String selectTagName = select;
250     
251         NodeList matchers = matchersElement.getElementsByTagName("matcher");
252         for ( int i = 0; i < matchers.getLength(); i++ ) {
253             // Get the values for this matcher element.
254             Element matcher = (Element)matchers.item(i);
255             String matchName = matcher.getAttribute("for");
256             StringBuffer selectTagPatternBuffer =
257                 new StringBuffer(64)
258                         .append("/")
259                         .append(matcher.getAttribute("match"))
260                         .append("/i");
261 
262             // If the select string matches the pattern, use the match
263             // name from this matcher.
264             if ( m_perl5Util.match(selectTagPatternBuffer.toString(), selectTagName) ) {
265                 return matchName;
266             }
267         }
268         return null;
269     }
270 
271     /***
272      * Replace substrings of one string with another string and return altered string.
273      * @param input input string
274      * @param find the string to replace
275      * @param replace the string to replace with
276      * @return the substituted string
277      */
278     static private String substituteSubString( String input, 
279                                                String find,
280                                                String replace )
281     {
282         int find_length = find.length();
283         int replace_length = replace.length();
284 
285         StringBuffer output = new StringBuffer(input);
286         int index = input.indexOf(find);
287         int outputOffset = 0;
288 
289         while ( index > -1 ) {
290             output.replace(index + outputOffset, index + outputOffset + find_length, replace);
291             outputOffset = outputOffset + (replace_length - find_length);
292 
293             index = input.indexOf(find, index + find_length);
294         }
295 
296         String result = output.toString();
297         return result;
298     }
299 
300     /***
301      * Returns a named string for the specified key.
302      * 
303      * @param name   the name of the String resource required.
304      * @return the requested resource
305      */
306     public String getString(String name)
307     {
308         return (String)m_resource.get(name);
309     }
310 
311     /***
312      * Returns a named string for the specified key.
313      * 
314      * @param name     the name of the String resource required.
315      * @param required true if the resource is required
316      * @return the requested resource
317      * @throws ConfigurationException
318      *         if a required resource cannot be found.
319      */
320     public String getString(String name, boolean required)
321     {
322         String str = getString(name);
323 
324         if (str == null && required) {
325             StringBuffer exceptionBuffer =
326                 new StringBuffer(64)
327                         .append("Required String resource: '")
328                         .append(name)
329                         .append("' was not found.");
330             throw new IllegalArgumentException(exceptionBuffer.toString());
331         }
332         return str;
333     }
334 
335     /***
336      * Returns a named string, replacing parameters with the values set in a Map.
337      * 
338      * @param name          the name of the String resource required.
339      * @param parameters    a map of parameters (name-value string pairs) which are
340      *                      replaced where found in the input strings
341      * @return the requested resource
342      */
343     public String getString(String name, Map parameters)
344     {
345         return replaceParameters(getString(name), parameters);
346     }
347 
348     /***
349      * Returns a named string, replacing parameters with the values set in a Map.
350      * 
351      * @param name          the name of the String resource required.
352      * @param parameters    a map of parameters (name-value string pairs) which are
353      *                      replaced where found in the input strings
354      * @return the requested resource
355      */
356     public String getString(String name, Map parameters, boolean required)
357     {
358         return replaceParameters(getString(name, required), parameters);
359     }
360 
361     /***
362      * Returns a named string, replacing parameters with the values set.
363      * 
364      * @param name          the name of the String resource required.
365      * @param parameters    a map of parameters (name-value string pairs) which are
366      *                      replaced where found in the input strings
367      * @return the requested resource
368      */
369     static public String replaceParameters(String str, Map parameters)
370     {
371         if (str != null && parameters != null) {
372             // Do parameter replacements for this string resource.
373             Iterator paramNames = parameters.keySet().iterator();
374             StringBuffer replaceBuffer = new StringBuffer(64);
375             while ( paramNames.hasNext() ) {
376                 String paramName = (String)paramNames.next();
377                 String paramValue = (String)parameters.get(paramName);
378                 replaceBuffer.append("${").append(paramName).append("}");
379                 str = substituteSubString(str, replaceBuffer.toString(), paramValue);
380                 if (paramNames.hasNext()) replaceBuffer.setLength(0);
381             }
382         }
383 
384         return str;
385     }
386 }