View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.util;
23  
24  import org.osgi.framework.Bundle;
25  import org.osgi.framework.BundleContext;
26  import org.slf4j.Logger;
27  import org.slf4j.LoggerFactory;
28  
29  import java.util.Dictionary;
30  import java.util.Enumeration;
31  import java.util.Properties;
32  import java.util.regex.Matcher;
33  import java.util.regex.Pattern;
34  
35  /**
36   * An extension to the {@code Properties} class which performs the following when you call getProperty:
37   *
38   * 1. Checks to see if there are any variables in the property 2. If so it replaces the variable with with the first
39   * match it finds in the following order - The java properties (java -Dkey=value) - The component context properties
40   * (set using setBundleContext) - The properties set in the object itself - The container's environment variables This
41   * class operates identically to a standard Properties object in all other respects.
42   */
43  public class XProperties extends Properties {
44  
45    private static final long serialVersionUID = -7497116948581078334L;
46  
47    public static final String START_REPLACEMENT = "${";
48  
49    public static final String END_REPLACEMENT = "}";
50  
51    /** Logging facility provided by log4j */
52    private static final Logger log = LoggerFactory.getLogger(XProperties.class);
53  
54    /** The {@code BundleContext} for this properties object */
55    private transient BundleContext context = null;
56  
57    /** The {@link Bundle} that loaded this object */
58    private transient Bundle bundle = null;
59  
60    /**
61     * {@inheritDoc} See the class description for more details.
62     *
63     * @see java.util.Properties#getProperty(java.lang.String)
64     */
65    @Override
66    public String getProperty(String key) {
67      String prop = getUninterpretedProperty(key);
68      if (prop != null) {
69        int start = prop.indexOf(START_REPLACEMENT);
70        while (start != -1) {
71          int end = prop.indexOf(END_REPLACEMENT);
72          int next = prop.indexOf(START_REPLACEMENT, start + START_REPLACEMENT.length());
73          if (next > 0 && next <= end) {
74            log.error("Start of next subkey before end of last subkey, unable to resolve replacements for key {}!", key);
75            return null;
76          }
77          String subkey = prop.substring(start + START_REPLACEMENT.length(), end);
78          prop = findReplacement(prop, subkey);
79          if (prop == null) {
80            log.error("Unable to find replacement for subkey {} in key {}, returning null!", subkey, key);
81            return null;
82          }
83          start = prop.indexOf(START_REPLACEMENT);
84        }
85      }
86      return prop;
87    }
88  
89    /**
90     * Wrapper around the actual search and replace functionality. This function will value with all of the instances of
91     * subkey replaced.
92     *
93     * @param value
94     *          The original string you wish to replace.
95     * @param subkey
96     *          The substring you wish to replace. This must be the substring rather than the full variable - M2_REPO
97     *          rather than ${M2_REPO}
98     * @return The value string with all instances of subkey replaced, or null in the case of an error.
99     */
100   private String findReplacement(String value, String subkey) {
101     if (subkey == null) {
102       return null;
103     }
104     Pattern p = Pattern.compile(START_REPLACEMENT + subkey + END_REPLACEMENT, Pattern.LITERAL);
105     String replacement = null;
106 
107     if (System.getProperty(subkey) != null) {
108       replacement = System.getProperty(subkey);
109     } else if (this.getProperty(subkey) != null) {
110       replacement = this.getProperty(subkey);
111     } else if (this.context != null && this.bundle != null && this.bundle.getState() == Bundle.ACTIVE
112             && this.context.getProperty(subkey) != null) {
113       replacement = this.context.getProperty(subkey);
114     } else if (System.getenv(subkey) != null) {
115       replacement = System.getenv(subkey);
116     }
117 
118     if (replacement != null)
119       return p.matcher(value).replaceAll(Matcher.quoteReplacement(replacement));
120     else
121       return null;
122   }
123 
124   /**
125    * A wrapper around the old getProperty behaviour, this method does not do any variable expansion.
126    *
127    * @param key
128    *          The key of the property
129    * @return The property exactly as it appears in the properties list without any variable expansion
130    */
131   public String getUninterpretedProperty(String key) {
132     return super.getProperty(key);
133   }
134 
135   /**
136    * Merges the properties from p into this properties object
137    *
138    * @param p
139    *          The {@code Dictionary} you wish to add to this object
140    */
141   public void merge(Dictionary<String, String> p) {
142     Enumeration<String> keys = p.keys();
143     while (keys.hasMoreElements()) {
144       String key = keys.nextElement();
145       this.put(key, p.get(key));
146     }
147   }
148 
149   /**
150    * Sets the {@code BundleContext} for this object. Set this to null if you wish to skip checking the context for a
151    * property.
152    *
153    * @param ctx
154    *          The {@code BundleContext} for this instance.
155    */
156   public void setBundleContext(BundleContext ctx) {
157     context = ctx;
158     if (ctx != null) {
159       bundle = ctx.getBundle();
160     }
161   }
162 
163   /**
164    * Return the current {@code BundleContext} that's in use by this object.
165    *
166    * @return The current {@code BundleContext}
167    */
168   public BundleContext getBundleContext() {
169     return this.context;
170   }
171 }