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