View Javadoc

1   /*
2    * Copyright 2012-2013 smartics, Kronseder & Reiner GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package de.smartics.properties.impl.config.domain.key.envapp;
17  
18  import static de.smartics.properties.api.core.domain.ConfigMessageBean.systemId;
19  
20  import static de.smartics.properties.api.core.domain.ConfigMessageBean.duplicate;
21  import java.io.BufferedInputStream;
22  import java.io.InputStream;
23  import java.net.URL;
24  import java.util.ArrayList;
25  import java.util.HashSet;
26  import java.util.List;
27  import java.util.Set;
28  
29  import org.apache.commons.io.IOUtils;
30  import org.apache.commons.lang.StringUtils;
31  import org.jdom.Element;
32  import org.jdom.Namespace;
33  
34  import de.smartics.properties.api.config.domain.key.ApplicationId;
35  import de.smartics.properties.api.config.domain.key.ConfigurationKey;
36  import de.smartics.properties.api.config.domain.key.EnvironmentId;
37  import de.smartics.properties.api.core.domain.ConfigCode;
38  import de.smartics.properties.api.core.domain.ConfigException;
39  import de.smartics.properties.api.core.domain.DuplicateDefaultKeyException;
40  import de.smartics.properties.api.core.domain.PropertiesContext;
41  import de.smartics.properties.spi.config.definition.DefinitionConfigParser;
42  import de.smartics.properties.spi.config.definition.PropertiesDefinitionContext;
43  import de.smartics.properties.spi.config.domain.key.PathMatcher;
44  import de.smartics.properties.spi.config.domain.key.PropertiesResourceMatcher;
45  import de.smartics.properties.spi.config.domain.key.PropertyResourceMatchers;
46  import de.smartics.properties.spi.core.util.ClassLoaderUtils;
47  import de.smartics.util.lang.classpath.ClassPathContext;
48  
49  /**
50   * Base implementation for parsing a <code>definition.xml</code> file for
51   * properties.
52   *
53   * @param <T> the type of context returned by the parser.
54   */
55  public abstract class AbstractDefinitionConfigParser<T extends PropertiesDefinitionContext>
56      implements DefinitionConfigParser<T>
57  {
58    // ********************************* Fields *********************************
59  
60    // --- constants ------------------------------------------------------------
61  
62    // --- members --------------------------------------------------------------
63  
64    /**
65     * The namespace of definition files parsed by this parser.
66     */
67    protected final Namespace namespace;
68  
69    // ****************************** Initializer *******************************
70  
71    // ****************************** Constructors ******************************
72  
73    /**
74     * Default constructor.
75     *
76     * @param namespace the namespace of definition files parsed by this parser.
77     */
78    protected AbstractDefinitionConfigParser(final Namespace namespace)
79    {
80      this.namespace = namespace;
81    }
82  
83    // ****************************** Inner Classes *****************************
84  
85    // ********************************* Methods ********************************
86  
87    // --- init -----------------------------------------------------------------
88  
89    // --- get&set --------------------------------------------------------------
90  
91    // --- business -------------------------------------------------------------
92  
93    /**
94     * Convenience method using a class' class loader to locate the configuration
95     * file to parse the configuration file at
96     * {@link PropertiesContext#DEFINITION_FILE}.
97     *
98     * @param type the type whose class loader to use to locate the configuration
99     *          file.
100    * @return the read configuration, never <code>null</code>.
101    * @throws ConfigException on any problem loading the file.
102    */
103   @Override
104   public final T parse(final Class<?> type) throws ConfigException
105   {
106     final ClassLoader loader = type.getClassLoader();
107     final String path = ClassLoaderUtils.calcArchivePath(type);
108     return parse(new ClassPathContext(loader, path));
109   }
110 
111   /**
112    * Parses the configuration file at {@link PropertiesContext#DEFINITION_FILE}.
113    *
114    * @param context the class loader with its context to use to locate the
115    *          configuration file.
116    * @return the read configuration, never <code>null</code>.
117    * @throws ConfigException on any problem loading the file.
118    */
119   @Override
120   public final T parse(final ClassPathContext context) throws ConfigException
121   {
122     final URL url = context.getResource(PropertiesContext.DEFINITION_FILE);
123     if (url != null)
124     {
125       final String systemId = url.toExternalForm();
126       InputStream in = null;
127       try
128       {
129         in =
130             new BufferedInputStream(
131                 context.getResourceAsStream(PropertiesContext.DEFINITION_FILE));
132         return parse(systemId, in);
133       }
134       finally
135       {
136         IOUtils.closeQuietly(in);
137       }
138     }
139     throw new ConfigException(systemId(ConfigCode.CONFIG_FILE_NOT_FOUND,
140         PropertiesContext.DEFINITION_FILE));
141   }
142 
143   /**
144    * Parses a set.
145    *
146    * @param rootNode the root node that contains the set.
147    * @param setGi the name of the element that is the set.
148    * @param elementGi the name of the elements within the set.
149    * @return the parsed set.
150    */
151   protected final Set<String> readSet(final Element rootNode,
152       final String setGi, final String elementGi)
153   {
154     return readSet(rootNode, setGi, elementGi, new HashSet<String>());
155   }
156 
157   /**
158    * Parses a set.
159    *
160    * @param rootNode the root node that contains the set.
161    * @param setGi the name of the element that is the set.
162    * @param elementGi the name of the elements within the set.
163    * @param defaultSet the set to return if no set is found within the root
164    *          node.
165    * @return the parsed set.
166    */
167   @SuppressWarnings("unchecked")
168   protected final Set<String> readSet(final Element rootNode,
169       final String setGi, final String elementGi, final Set<String> defaultSet)
170   {
171     final Element set = rootNode.getChild(setGi, namespace);
172     if (set != null)
173     {
174       final Set<String> collection = new HashSet<String>();
175       final List<Element> elements = set.getChildren(elementGi, namespace);
176       for (final Element element : elements)
177       {
178         final String text = element.getTextNormalize();
179         collection.add(text);
180       }
181 
182       return collection;
183     }
184 
185     return defaultSet;
186   }
187 
188   /**
189    * Parsed the map of files from the given element.
190    *
191    * @param systemId the identifier of the configuration file that is parsed.
192    * @param rootNode the node within which the files are.
193    * @return the map of files.
194    * @throws ConfigException if the configuration is not valid.
195    */
196   @SuppressWarnings("unchecked")
197   protected final PropertyResourceMatchers readFiles(final String systemId,
198       final Element rootNode) throws ConfigException
199   {
200     final List<PropertiesResourceMatcher> matchers =
201         new ArrayList<PropertiesResourceMatcher>();
202     ConfigurationKey<?> anyKey = null;
203 
204     final Element keySet = rootNode.getChild("key-set", namespace);
205     if (keySet != null)
206     {
207       final List<Element> elements = keySet.getChildren("key", namespace);
208       for (final Element element : elements)
209       {
210         final ConfigurationKey<?> key = readKey(element);
211         final Element files = element.getChild("files", namespace);
212         if (files != null)
213         {
214           final List<Element> fileElements =
215               files.getChildren("file", namespace);
216           final List<PathMatcher.Matcher> pathMatcher =
217               new ArrayList<PathMatcher.Matcher>(fileElements.size());
218           for (final Element file : fileElements)
219           {
220             final String text = file.getTextNormalize();
221             final PathMatcher.Matcher matcher = new PathMatcher.Matcher(text);
222             pathMatcher.add(matcher);
223           }
224           final PropertiesResourceMatcher matcher =
225               PathMatcher.forMatchers(key, pathMatcher);
226           matchers.add(matcher);
227         }
228         else
229         {
230           checkDuplication(systemId, anyKey, key);
231           anyKey = key;
232         }
233       }
234     }
235 
236     return new PropertyResourceMatchers(anyKey, matchers);
237   }
238 
239   private void checkDuplication(final String systemId,
240       final ConfigurationKey<?> anyKey, final ConfigurationKey<?> key)
241   {
242     if (anyKey != null)
243     {
244       throw new DuplicateDefaultKeyException(duplicate(systemId,
245           anyKey.toString(), key.toString()));
246     }
247   }
248 
249   /**
250    * Parses the configuration key.
251    *
252    * @param element the element within which the elements of the key are
253    *          located.
254    * @return the configuration key.
255    */
256   protected abstract ConfigurationKey<?> readKey(Element element);
257 
258   /**
259    * Parses environment information.
260    *
261    * @param rootNode the element to parse from.
262    * @return the environment information.
263    */
264   protected final EnvironmentId readEnvironment(final Element rootNode)
265   {
266     final Element environment = rootNode.getChild("environment", namespace);
267 
268     final EnvironmentId environmentId;
269     if (environment != null)
270     {
271       final String name =
272           norm(environment.getChildTextNormalize("name", namespace));
273       final String node =
274           norm(environment.getChildTextNormalize("node", namespace));
275 
276       environmentId = new EnvironmentId(name, node);
277     }
278     else
279     {
280       environmentId = EnvironmentId.ANY_ENV;
281     }
282     return environmentId;
283   }
284 
285   /**
286    * Parses application information.
287    *
288    * @param rootNode the element to parse from.
289    * @return the application information.
290    */
291   protected final ApplicationId readApplication(final Element rootNode)
292   {
293     final Element application = rootNode.getChild("application", namespace);
294 
295     final ApplicationId applicationId;
296     if (application != null)
297     {
298       final String group =
299           norm(application.getChildTextNormalize("group", namespace));
300       final String artifact =
301           norm(application.getChildTextNormalize("artifact", namespace));
302       final String version =
303           norm(application.getChildTextNormalize("version", namespace));
304       applicationId = new ApplicationId(group, artifact, version);
305     }
306     else
307     {
308       applicationId = ApplicationId.ANY_APP;
309     }
310     return applicationId;
311   }
312 
313   /**
314    * Normalizes the text.
315    *
316    * @param text the text to normalize.
317    * @return <code>null</code> if {@code text} is blank, the text otherwise.
318    */
319   protected static final String norm(final String text)
320   {
321     if (StringUtils.isBlank(text))
322     {
323       return null;
324     }
325     return text;
326   }
327 
328   // --- object basics --------------------------------------------------------
329 
330 }