View Javadoc
1   /**
2    * This file Copyright (c) 2011-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.init;
35  
36  import java.util.ArrayList;
37  import java.util.HashSet;
38  import java.util.List;
39  import java.util.Set;
40  
41  /**
42   * Abstract implementation, providing the basic behavior expected from a {@link MagnoliaConfigurationProperties} implementation.
43   *
44   * TODO: cache results.
45   */
46  public abstract class AbstractMagnoliaConfigurationProperties implements MagnoliaConfigurationProperties {
47      protected static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractMagnoliaConfigurationProperties.class);
48  
49      protected List<PropertySource> sources;
50  
51      protected AbstractMagnoliaConfigurationProperties(List<PropertySource> propertySources) {
52          this.sources = propertySources;
53      }
54  
55      protected AbstractMagnoliaConfigurationProperties() {
56          this(new ArrayList<PropertySource>());
57      }
58  
59      @Override
60      public void init() throws Exception {
61      }
62  
63      @Override
64      public Set<String> getKeys() {
65          final Set<String> allKeys = new HashSet<String>();
66          for (PropertySource source : sources) {
67              allKeys.addAll(source.getKeys());
68          }
69          return allKeys;
70      }
71  
72      @Override
73      public PropertySource getPropertySource(String key) {
74          for (PropertySource source : sources) {
75              if (source.hasProperty(key)) {
76                  return source;
77              }
78          }
79          return null;
80      }
81  
82      @Override
83      public String getProperty(String key) {
84          final PropertySource propertySource = getPropertySource(key);
85          if (propertySource != null) {
86              final String value = propertySource.getProperty(key);
87              return parseStringValue(value, new HashSet<String>());
88          }
89          return null;
90      }
91  
92      @Override
93      public boolean getBooleanProperty(String property) {
94          return Boolean.parseBoolean(getProperty(property));
95      }
96  
97      @Override
98      public boolean hasProperty(String key) {
99          return getPropertySource(key) != null;
100     }
101 
102     @Override
103     public String describe() {
104         final StringBuilder s = new StringBuilder()
105                 .append("[")
106                 .append(getClass().getSimpleName())
107                 .append(" with sources: ");
108         for (PropertySource source : sources) {
109             s.append(source.describe());
110         }
111         s.append("]");
112         return s.toString();
113     }
114 
115     @Override
116     public String toString() {
117         return describe() + " with properties: " + sources;
118     }
119 
120     /**
121      * Parse the given String value recursively, to be able to resolve nested placeholders. Partly borrowed from
122      * org.springframework.beans.factory.config.PropertyPlaceholderConfigurer (original author: Juergen Hoeller)
123      */
124     protected String parseStringValue(String strVal, Set<String> visitedPlaceholders) {
125         final StringBuffer buf = new StringBuffer(strVal.trim());
126 
127         int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
128         while (startIndex != -1) {
129             int endIndex = -1;
130 
131             int index = startIndex + PLACEHOLDER_PREFIX.length();
132             int withinNestedPlaceholder = 0;
133             while (index < buf.length()) {
134                 if (PLACEHOLDER_SUFFIX.equals(buf.subSequence(index, index + PLACEHOLDER_SUFFIX.length()))) {
135                     if (withinNestedPlaceholder > 0) {
136                         withinNestedPlaceholder--;
137                         index = index + PLACEHOLDER_SUFFIX.length();
138                     } else {
139                         endIndex = index;
140                         break;
141                     }
142                 } else if (PLACEHOLDER_PREFIX.equals(buf.subSequence(index, index + PLACEHOLDER_PREFIX.length()))) {
143                     withinNestedPlaceholder++;
144                     index = index + PLACEHOLDER_PREFIX.length();
145                 } else {
146                     index++;
147                 }
148             }
149 
150             if (endIndex != -1) {
151                 String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
152                 if (!visitedPlaceholders.add(placeholder)) {
153                     log.warn("Circular reference detected in properties, \"{}\" is not resolvable", strVal);
154                     return strVal;
155                 }
156                 // Recursive invocation, parsing placeholders contained in the placeholder key.
157                 placeholder = parseStringValue(placeholder, visitedPlaceholders);
158                 // Now obtain the value for the fully resolved key...
159                 // Can't call getProperty() directly, as it would blow the call stack
160                 final PropertySource propertySource = getPropertySource(placeholder);
161                 String propVal = propertySource != null ? propertySource.getProperty(placeholder) : null;
162                 if (propVal != null) {
163                     // Recursive invocation, parsing placeholders contained in the
164                     // previously resolved placeholder value.
165                     propVal = parseStringValue(propVal, visitedPlaceholders);
166                     buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal);
167                     startIndex = buf.indexOf(PLACEHOLDER_PREFIX, startIndex + propVal.length());
168                 } else {
169                     // Proceed with unprocessed value.
170                     startIndex = buf.indexOf(PLACEHOLDER_PREFIX, endIndex + PLACEHOLDER_SUFFIX.length());
171                 }
172                 visitedPlaceholders.remove(placeholder);
173             } else {
174                 startIndex = -1;
175             }
176         }
177 
178         return buf.toString();
179     }
180 
181     protected static final String PLACEHOLDER_PREFIX = "${";
182     protected static final String PLACEHOLDER_SUFFIX = "}";
183 
184 
185 }