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   /**
126    * A wrapper around the old getProperty behaviour, this method does not do any variable expansion.
127    *
128    * @param key
129    *          The key of the property
130    * @return The property exactly as it appears in the properties list without any variable expansion
131    */
132   public String getUninterpretedProperty(String key) {
133     return super.getProperty(key);
134   }
135 
136   /**
137    * Merges the properties from p into this properties object
138    *
139    * @param p
140    *          The {@code Dictionary} you wish to add to this object
141    */
142   public void merge(Dictionary<String, String> p) {
143     Enumeration<String> keys = p.keys();
144     while (keys.hasMoreElements()) {
145       String key = keys.nextElement();
146       this.put(key, p.get(key));
147     }
148   }
149 
150   /**
151    * Sets the {@code BundleContext} for this object. Set this to null if you wish to skip checking the context for a
152    * property.
153    *
154    * @param ctx
155    *          The {@code BundleContext} for this instance.
156    */
157   public void setBundleContext(BundleContext ctx) {
158     context = ctx;
159     if (ctx != null) {
160       bundle = ctx.getBundle();
161     }
162   }
163 
164   /**
165    * Return the current {@code BundleContext} that's in use by this object.
166    *
167    * @return The current {@code BundleContext}
168    */
169   public BundleContext getBundleContext() {
170     return this.context;
171   }
172 }