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.spi.config.definition;
17  
18  import static de.smartics.properties.api.config.domain.key.ApplicationId.ANY_APP;
19  import static de.smartics.properties.api.config.domain.key.EnvironmentId.ANY_ENV;
20  
21  import java.io.Serializable;
22  import java.util.StringTokenizer;
23  
24  import javax.annotation.concurrent.ThreadSafe;
25  
26  import de.smartics.properties.api.config.domain.key.ApplicationId;
27  import de.smartics.properties.api.config.domain.key.ConfigurationKey;
28  import de.smartics.properties.api.config.domain.key.EnvironmentId;
29  import de.smartics.util.lang.Arguments;
30  
31  /**
32   * Derives a {@link ConfigurationKey} from a path found in a definition file.
33   */
34  @ThreadSafe
35  public final class DefinitionKeyHelper implements Serializable
36  {
37    // ********************************* Fields *********************************
38  
39    // --- constants ------------------------------------------------------------
40  
41    /**
42     * The class version identifier.
43     */
44    private static final long serialVersionUID = 1L;
45  
46    // --- members --------------------------------------------------------------
47  
48    /**
49     * The context to evaluate the configuration keys from properties file paths.
50     *
51     * @serial
52     */
53    private final PropertiesDefinitionContext context;
54  
55    // ****************************** Initializer *******************************
56  
57    // ****************************** Constructors ******************************
58  
59    /**
60     * Convenience constructor using the default TLDs and not registering any
61     * environments, nodes or groups.
62     */
63    public DefinitionKeyHelper()
64    {
65      this(new PropertiesDefinitionContext());
66    }
67  
68    /**
69     * Default constructor.
70     *
71     * @param context the context to evaluate the configuration keys from
72     *          properties file paths.
73     * @throws NullPointerException if {@code context} is <code>null</code>.
74     */
75    public DefinitionKeyHelper(final PropertiesDefinitionContext context)
76      throws NullPointerException
77    {
78      Arguments.checkNotNull("context", context);
79  
80      this.context = context;
81    }
82  
83    // ****************************** Inner Classes *****************************
84  
85    // ********************************* Methods ********************************
86  
87    // --- init -----------------------------------------------------------------
88  
89    // --- get&set --------------------------------------------------------------
90  
91    // --- business -------------------------------------------------------------
92  
93    /**
94     * Parses the given path to create a configuration key.
95     * <p>
96     * The expected syntax is as follows:
97     * </p>
98     * <ol>
99     * <li><code>/environment</code></li>
100    * <li><code>/environment/node</code></li>
101    * <li><code>/environment/node/group</code></li>
102    * <li><code>/environment/node/group/application</code></li>
103    * <li><code>/environment/node/group/application/version</code></li>
104    * <li><code>/environment/group</code></li>
105    * <li><code>/environment/group/application</code></li>
106    * <li><code>/environment/group/application/version</code></li>
107    * <li><code>/group</code></li>
108    * <li><code>/group/application</code></li>
109    * <li><code>/group/application/version</code></li>
110    * </ol>
111    * <p>
112    * A file ending with properties following the path will be chopped.
113    * </p>
114    * <p>
115    * The parser has to determine whether a part of the path is a
116    * <code>environment</code>, a <code>node</code> or a <code>group</code>.
117    * Since a <code>node</code> is always prefixed by an <code>environment</code>
118    * only the following two cases have to be dealt with:
119    * </p>
120    * <ol>
121    * <li><code>environment</code> vs. <code>group</code></li>
122    * <li><code>node</code> vs. <code>group</code></li>
123    * </ol>
124    * <p>
125    * <code>group</code>s start with
126    * </p>
127    * <ol>
128    * <li>A TLD as registered by default by
129    * {@link PropertiesDefinitionContext#DEFAULT_TLDS} or explicitly
130    * registered with {@link PropertiesDefinitionContext}</li>
131    * <li>Two letters followed by a dot (<code>.</code>)</li>
132    * <li>Any sequence of characters that is explicitly registered as a group in
133    * the <code>definition.xml</code> file</li>
134    * </ol>
135    * <p>
136    * <code>environment</code>s and <code>node</code>s do not start as
137    * <code>groups</code> except they are explicitly registered in the
138    * <code>definition.xml</code> file.
139    * </p>
140    *
141    * @param pathWithFile the path to parse.
142    * @return the configuration key.
143    * @throws IllegalArgumentException if the given {@code path} is not valid
144    *           according to the rules given above.
145    */
146   public ConfigurationKey parse(final String pathWithFile)
147     throws IllegalArgumentException
148   {
149     final ConfigurationKey explicitKey = fetchExplicitKey(pathWithFile);
150     if (explicitKey != null)
151     {
152       return explicitKey;
153     }
154 
155     final String path = chopFile(pathWithFile);
156 
157     final StringTokenizer tokenizer = new StringTokenizer(path, "/");
158     if (tokenizer.hasMoreTokens())
159     {
160       final ConfigurationKey key;
161 
162       final String token = tokenizer.nextToken();
163       if (isGroup(token))
164       {
165         key = parseApplicationKey(token, tokenizer);
166       }
167       else
168       {
169         key = parseEnvironmentKey(token, tokenizer);
170       }
171 
172       return key;
173     }
174 
175     return new ConfigurationKey(EnvironmentId.ANY_ENV, ApplicationId.ANY_APP);
176     // throw new IllegalArgumentException(
177     // "Path '" + path + "' does not contain an environment or group.");
178   }
179 
180   private static String chopFile(final String pathWithFile)
181   {
182     if (pathWithFile.endsWith(".properties"))
183     {
184       final int lastSlash = pathWithFile.lastIndexOf('/');
185       if (lastSlash != -1)
186       {
187         final String path = pathWithFile.substring(0, lastSlash);
188         return path;
189       }
190 
191       return "";
192     }
193     return pathWithFile;
194   }
195 
196   private ConfigurationKey fetchExplicitKey(final String path)
197   {
198     ConfigurationKey key = context.getKey(path);
199     if (key == null)
200     {
201       key = context.getKey(null);
202     }
203     return key;
204   }
205 
206   private boolean isGroup(final String token)
207   {
208     if (context.isRegisteredEnvironment(token)
209         || context.isRegisteredNode(token))
210     {
211       return false;
212     }
213 
214     return context.isGroup(token);
215   }
216 
217   private ConfigurationKey parseEnvironmentKey(final String environment,
218       final StringTokenizer tokenizer)
219   {
220     final EnvironmentId envId;
221     final ApplicationId appId;
222     if (tokenizer.hasMoreTokens())
223     {
224       final String token = tokenizer.nextToken();
225       if (isGroup(token))
226       {
227         envId = new EnvironmentId(environment);
228         appId = parseAppId(token, tokenizer);
229       }
230       else
231       {
232         envId = new EnvironmentId(environment, token);
233         if (tokenizer.hasMoreTokens())
234         {
235           appId = parseAppId(tokenizer.nextToken(), tokenizer);
236         }
237         else
238         {
239           appId = ANY_APP;
240         }
241       }
242     }
243     else
244     {
245       envId = new EnvironmentId(environment);
246       appId = ANY_APP;
247     }
248 
249     final ConfigurationKey key = new ConfigurationKey(envId, appId);
250 
251     return key;
252   }
253 
254   private ApplicationId parseAppId(final String group,
255       final StringTokenizer tokenizer)
256   {
257     String artifact = null;
258     String version = null;
259     if (tokenizer.hasMoreTokens())
260     {
261       artifact = tokenizer.nextToken();
262       if (tokenizer.hasMoreTokens())
263       {
264         version = tokenizer.nextToken();
265       }
266     }
267 
268     return new ApplicationId(group, artifact, version);
269   }
270 
271   private ConfigurationKey parseApplicationKey(final String group,
272       final StringTokenizer tokenizer)
273   {
274     final EnvironmentId envId = ANY_ENV;
275     final ApplicationId appId = parseAppId(group, tokenizer);
276 
277     return new ConfigurationKey(envId, appId);
278   }
279 
280   // --- object basics --------------------------------------------------------
281 
282 }