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 }