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.rtaware;
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 TenantUserDefinitionKeyHelper implements Serializable,
38      DefinitionKeyHelper
39  { // NOPMD
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 TenantUserPropertiesDefinitionContext 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 TenantUserDefinitionKeyHelper()
67    {
68      this(new TenantUserPropertiesDefinitionContext());
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 TenantUserDefinitionKeyHelper(
79        final TenantUserPropertiesDefinitionContext 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>/'user.'user</code></li>
102    * <li><code>/'tenant.'tenant</code></li>
103    * <li><code>/environment</code></li>
104    * <li><code>/environment</code></li>
105    * <li><code>/environment/node</code></li>
106    * <li><code>/environment/node/group</code></li>
107    * <li><code>/environment/node/group/application</code></li>
108    * <li><code>/environment/node/group/application/version</code></li>
109    * <li><code>/environment/group</code></li>
110    * <li><code>/environment/group/application</code></li>
111    * <li><code>/environment/group/application/version</code></li>
112    * <li><code>/group</code></li>
113    * <li><code>/group/application</code></li>
114    * <li><code>/group/application/version</code></li>
115    * </ol>
116    * <p>
117    * A file ending with properties following the path will be chopped.
118    * </p>
119    * <p>
120    * The parser has to determine whether a part of the path is a
121    * <code>environment</code>, a <code>node</code> or a <code>group</code>.
122    * Since a <code>node</code> is always prefixed by an <code>environment</code>
123    * only the following two cases have to be dealt with:
124    * </p>
125    * <ol>
126    * <li><code>environment</code> vs. <code>group</code></li>
127    * <li><code>node</code> vs. <code>group</code></li>
128    * </ol>
129    * <p>
130    * <code>group</code>s start with
131    * </p>
132    * <ol>
133    * <li>A TLD as registered by default by
134    * {@link TenantUserPropertiesDefinitionContext#DEFAULT_TLDS} or explicitly
135    * registered with {@link TenantUserPropertiesDefinitionContext}</li>
136    * <li>Two letters followed by a dot (<code>.</code>)</li>
137    * <li>Any sequence of characters that is explicitly registered as a group in
138    * the <code>definition.xml</code> file</li>
139    * </ol>
140    * <p>
141    * <code>environment</code>s and <code>node</code>s do not start as
142    * <code>groups</code> except they are explicitly registered in the
143    * <code>definition.xml</code> file.
144    * </p>
145    *
146    * @param pathWithFile the path to parse.
147    * @return the configuration key.
148    * @throws IllegalArgumentException if the given {@code path} is not valid
149    *           according to the rules given above.
150    */
151   @Override
152   public ConfigurationKey<?> parse(final String pathWithFile)
153     throws IllegalArgumentException
154   {
155     final ConfigurationKey<?> explicitKey = fetchExplicitKey(pathWithFile);
156     if (explicitKey != null)
157     {
158       return explicitKey;
159     }
160 
161     final String path = chopFile(pathWithFile);
162 
163     final StringTokenizer tokenizer = new StringTokenizer(path, "/");
164     final UserId userId = UserId.ANY_USER;
165     final TenantId tenantId = TenantId.ANY_TENANT;
166     if (tokenizer.hasMoreTokens())
167     {
168       final ConfigurationKey<?> key;
169 
170       final String token = tokenizer.nextToken();
171       if (isUser(token))
172       {
173         key = parseUserKey(token, tokenizer);
174       }
175       else if (isTenant(token))
176       {
177         key = parseTenantKey(userId, token, tokenizer);
178       }
179       else if (isGroup(token))
180       {
181         key = parseApplicationKey(userId, tenantId, token, tokenizer);
182       }
183       else
184       {
185         key = parseEnvironmentKey(userId, tenantId, token, tokenizer);
186       }
187 
188       return key;
189     }
190 
191     return ConfigurationKeyContextManager.INSTANCE.context()
192         .configurationKeyFactory().createDefaultKey();
193     // throw new IllegalArgumentException(
194     // "Path '" + path + "' does not contain an environment or group.");
195   }
196 
197   private boolean isUser(final String token)
198   {
199     return token.startsWith("user.");
200   }
201 
202   private boolean isTenant(final String token)
203   {
204     return token.startsWith("tenant.");
205   }
206 
207   private static String chopFile(final String pathWithFile)
208   {
209     if (pathWithFile.endsWith(".properties"))
210     {
211       final int lastSlash = pathWithFile.lastIndexOf('/');
212       if (lastSlash != -1)
213       {
214         final String path = pathWithFile.substring(0, lastSlash);
215         return path;
216       }
217 
218       return "";
219     }
220     return pathWithFile;
221   }
222 
223   private ConfigurationKey<?> fetchExplicitKey(final String path)
224   {
225     ConfigurationKey<?> key = context.getKey(path);
226     if (key == null)
227     {
228       key = context.getKey(null);
229     }
230     return key;
231   }
232 
233   private boolean isGroup(final String token)
234   {
235     if (context.isRegisteredEnvironment(token)
236         || context.isRegisteredNode(token))
237     {
238       return false;
239     }
240 
241     return context.isGroup(token);
242   }
243 
244   private UserId parseUserId(final String user)
245   {
246     final UserId id = new UserId(user.substring(user.indexOf('.') + 1));
247     return id;
248   }
249 
250   private TenantId parseTenantId(final String tenant)
251   {
252     final TenantId id = new TenantId(tenant.substring(tenant.indexOf('.') + 1));
253     return id;
254   }
255 
256   private ConfigurationKey<?> parseUserKey(final String user,
257       final StringTokenizer tokenizer)
258   {
259     final UserId userId = parseUserId(user);
260     final TenantId tenantId = TenantId.ANY_TENANT;
261     final ConfigurationKey<?> key;
262     if (tokenizer.hasMoreTokens())
263     {
264 
265       final String token = tokenizer.nextToken();
266       if (isTenant(token))
267       {
268         key = parseTenantKey(userId, token, tokenizer);
269       }
270       else if (isGroup(token))
271       {
272         key = parseApplicationKey(userId, tenantId, token, tokenizer);
273       }
274       else
275       {
276         key = parseEnvironmentKey(userId, tenantId, token, tokenizer);
277       }
278     }
279     else
280     {
281       key =
282           new TenantUserConfigurationKey(EnvironmentId.ANY_ENV,
283               ApplicationId.ANY_APP, tenantId, userId);
284     }
285 
286     return key;
287   }
288 
289   private ConfigurationKey<?> parseTenantKey(final UserId userId,
290       final String tenant, final StringTokenizer tokenizer)
291   {
292     final TenantId tenantId = parseTenantId(tenant);
293 
294     final ConfigurationKey<?> key;
295     if (tokenizer.hasMoreTokens())
296     {
297       final String token = tokenizer.nextToken();
298       if (isGroup(token))
299       {
300         key = parseApplicationKey(userId, tenantId, token, tokenizer);
301       }
302       else
303       {
304         key = parseEnvironmentKey(userId, tenantId, token, tokenizer);
305       }
306     }
307     else
308     {
309       key =
310           new TenantUserConfigurationKey(EnvironmentId.ANY_ENV,
311               ApplicationId.ANY_APP, tenantId, userId);
312     }
313 
314     return key;
315   }
316 
317   private ConfigurationKey<?> parseEnvironmentKey(final UserId userId,
318       final TenantId tenantId, final String environment,
319       final StringTokenizer tokenizer)
320   {
321     final EnvironmentId envId;
322     final ApplicationId appId;
323     if (tokenizer.hasMoreTokens())
324     {
325       final String token = tokenizer.nextToken();
326       if (isGroup(token))
327       {
328         envId = new EnvironmentId(environment);
329         appId = parseAppId(token, tokenizer);
330       }
331       else
332       {
333         envId = new EnvironmentId(environment, token);
334         if (tokenizer.hasMoreTokens())
335         {
336           appId = parseAppId(tokenizer.nextToken(), tokenizer);
337         }
338         else
339         {
340           appId = ANY_APP;
341         }
342       }
343     }
344     else
345     {
346       envId = new EnvironmentId(environment);
347       appId = ANY_APP;
348     }
349 
350     final ConfigurationKey<?> key =
351         new TenantUserConfigurationKey(envId, appId, tenantId, userId);
352 
353     return key;
354   }
355 
356   private ApplicationId parseAppId(final String group,
357       final StringTokenizer tokenizer)
358   {
359     String artifact = null;
360     String version = null;
361     if (tokenizer.hasMoreTokens())
362     {
363       artifact = tokenizer.nextToken();
364       if (tokenizer.hasMoreTokens())
365       {
366         version = tokenizer.nextToken();
367       }
368     }
369 
370     return new ApplicationId(group, artifact, version);
371   }
372 
373   private ConfigurationKey<?> parseApplicationKey(final UserId userId,
374       final TenantId tenantId, final String group,
375       final StringTokenizer tokenizer)
376   {
377     final EnvironmentId envId = ANY_ENV;
378     final ApplicationId appId = parseAppId(group, tokenizer);
379 
380     return new TenantUserConfigurationKey(envId, appId, tenantId, userId);
381   }
382 
383   // --- object basics --------------------------------------------------------
384 
385 }