001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.configuration;
016    
017    import com.germinus.easyconf.ComponentConfiguration;
018    import com.germinus.easyconf.ComponentProperties;
019    
020    import com.liferay.portal.configuration.easyconf.ClassLoaderAggregateProperties;
021    import com.liferay.portal.configuration.easyconf.ClassLoaderComponentConfiguration;
022    import com.liferay.portal.kernel.configuration.Filter;
023    import com.liferay.portal.kernel.log.Log;
024    import com.liferay.portal.kernel.log.LogFactoryUtil;
025    import com.liferay.portal.kernel.util.ArrayUtil;
026    import com.liferay.portal.kernel.util.PropertiesUtil;
027    import com.liferay.portal.kernel.util.StringBundler;
028    import com.liferay.portal.kernel.util.Validator;
029    import com.liferay.portal.model.Company;
030    import com.liferay.portal.model.CompanyConstants;
031    import com.liferay.portal.service.CompanyLocalServiceUtil;
032    
033    import java.lang.reflect.Field;
034    
035    import java.util.HashSet;
036    import java.util.Iterator;
037    import java.util.List;
038    import java.util.Map;
039    import java.util.Properties;
040    import java.util.Set;
041    import java.util.concurrent.ConcurrentHashMap;
042    
043    import org.apache.commons.configuration.CompositeConfiguration;
044    import org.apache.commons.configuration.Configuration;
045    import org.apache.commons.configuration.MapConfiguration;
046    
047    /**
048     * @author Brian Wing Shun Chan
049     * @author Shuyang Zhou
050     */
051    public class ConfigurationImpl
052            implements com.liferay.portal.kernel.configuration.Configuration {
053    
054            public ConfigurationImpl(ClassLoader classLoader, String name) {
055                    this(classLoader, name, CompanyConstants.SYSTEM);
056            }
057    
058            public ConfigurationImpl(
059                    ClassLoader classLoader, String name, long companyId) {
060    
061                    String webId = null;
062    
063                    if (companyId > CompanyConstants.SYSTEM) {
064                            try {
065                                    Company company = CompanyLocalServiceUtil.getCompanyById(
066                                            companyId);
067    
068                                    webId = company.getWebId();
069                            }
070                            catch (Exception e) {
071                                    _log.error(e, e);
072                            }
073                    }
074    
075                    _componentConfiguration = new ClassLoaderComponentConfiguration(
076                            classLoader, webId, name);
077    
078                    printSources(companyId, webId);
079            }
080    
081            @Override
082            public void addProperties(Properties properties) {
083                    try {
084                            ComponentProperties componentProperties =
085                                    _componentConfiguration.getProperties();
086    
087                            ClassLoaderAggregateProperties classLoaderAggregateProperties =
088                                    (ClassLoaderAggregateProperties)
089                                            componentProperties.toConfiguration();
090    
091                            Field field1 = CompositeConfiguration.class.getDeclaredField(
092                                    "configList");
093    
094                            field1.setAccessible(true);
095    
096                            // Add to configList of base conf
097    
098                            List<Configuration> configurations =
099                                    (List<Configuration>)field1.get(classLoaderAggregateProperties);
100    
101                            MapConfiguration newConfiguration = new MapConfiguration(
102                                    properties);
103    
104                            configurations.add(0, newConfiguration);
105    
106                            // Add to configList of AggregatedProperties itself
107    
108                            CompositeConfiguration compositeConfiguration =
109                                    classLoaderAggregateProperties.getBaseConfiguration();
110    
111                            configurations = (List<Configuration>)field1.get(
112                                    compositeConfiguration);
113    
114                            configurations.add(0, newConfiguration);
115    
116                            clearCache();
117                    }
118                    catch (Exception e) {
119                            _log.error("The properties could not be added", e);
120                    }
121            }
122    
123            @Override
124            public void clearCache() {
125                    _values.clear();
126            }
127    
128            @Override
129            public boolean contains(String key) {
130                    Object value = _values.get(key);
131    
132                    if (value == null) {
133                            ComponentProperties componentProperties = getComponentProperties();
134    
135                            value = componentProperties.getProperty(key);
136    
137                            if (value == null) {
138                                    value = _nullValue;
139                            }
140    
141                            _values.put(key, value);
142                    }
143    
144                    if (value == _nullValue) {
145                            return false;
146                    }
147    
148                    return true;
149            }
150    
151            @Override
152            public String get(String key) {
153                    Object value = _values.get(key);
154    
155                    if (value == null) {
156                            ComponentProperties componentProperties = getComponentProperties();
157    
158                            value = componentProperties.getString(key);
159    
160                            if (value == null) {
161                                    value = _nullValue;
162                            }
163    
164                            _values.put(key, value);
165                    }
166                    else if (_PRINT_DUPLICATE_CALLS_TO_GET) {
167                            System.out.println("Duplicate call to get " + key);
168                    }
169    
170                    if (value instanceof String) {
171                            return (String)value;
172                    }
173    
174                    return null;
175            }
176    
177            @Override
178            public String get(String key, Filter filter) {
179                    String filterCacheKey = buildFilterCacheKey(key, filter, false);
180    
181                    Object value = null;
182    
183                    if (filterCacheKey != null) {
184                            value = _values.get(filterCacheKey);
185                    }
186    
187                    if (value == null) {
188                            ComponentProperties componentProperties = getComponentProperties();
189    
190                            value = componentProperties.getString(
191                                    key, getEasyConfFilter(filter));
192    
193                            if (filterCacheKey != null) {
194                                    if (value == null) {
195                                            value = _nullValue;
196                                    }
197    
198                                    _values.put(filterCacheKey, value);
199                            }
200                    }
201    
202                    if (value instanceof String) {
203                            return (String)value;
204                    }
205    
206                    return null;
207            }
208    
209            @Override
210            public String[] getArray(String key) {
211                    String cacheKey = _ARRAY_KEY_PREFIX.concat(key);
212    
213                    Object value = _values.get(cacheKey);
214    
215                    if (value == null) {
216                            ComponentProperties componentProperties = getComponentProperties();
217    
218                            String[] array = componentProperties.getStringArray(key);
219    
220                            value = fixArrayValue(cacheKey, array);
221                    }
222    
223                    if (value instanceof String[]) {
224                            return (String[])value;
225                    }
226    
227                    return _emptyArray;
228            }
229    
230            @Override
231            public String[] getArray(String key, Filter filter) {
232                    String filterCacheKey = buildFilterCacheKey(key, filter, true);
233    
234                    Object value = null;
235    
236                    if (filterCacheKey != null) {
237                            value = _values.get(filterCacheKey);
238                    }
239    
240                    if (value == null) {
241                            ComponentProperties componentProperties = getComponentProperties();
242    
243                            String[] array = componentProperties.getStringArray(
244                                    key, getEasyConfFilter(filter));
245    
246                            value = fixArrayValue(filterCacheKey, array);
247                    }
248    
249                    if (value instanceof String[]) {
250                            return (String[])value;
251                    }
252    
253                    return _emptyArray;
254            }
255    
256            @Override
257            public Properties getProperties() {
258    
259                    // For some strange reason, componentProperties.getProperties() returns
260                    // values with spaces after commas. So a property setting of "xyz=1,2,3"
261                    // actually returns "xyz=1, 2, 3". This can break applications that
262                    // don't expect that extra space. However, getting the property value
263                    // directly through componentProperties returns the correct value. This
264                    // method fixes the weird behavior by returning properties with the
265                    // correct values.
266    
267                    Properties properties = new Properties();
268    
269                    ComponentProperties componentProperties = getComponentProperties();
270    
271                    Properties componentPropertiesProperties =
272                            componentProperties.getProperties();
273    
274                    for (String key : componentPropertiesProperties.stringPropertyNames()) {
275                            properties.setProperty(key, componentProperties.getString(key));
276                    }
277    
278                    return properties;
279            }
280    
281            @Override
282            public Properties getProperties(String prefix, boolean removePrefix) {
283                    Properties properties = getProperties();
284    
285                    return PropertiesUtil.getProperties(properties, prefix, removePrefix);
286            }
287    
288            @Override
289            public void removeProperties(Properties properties) {
290                    try {
291                            ComponentProperties componentProperties =
292                                    _componentConfiguration.getProperties();
293    
294                            ClassLoaderAggregateProperties classLoaderAggregateProperties =
295                                    (ClassLoaderAggregateProperties)
296                                            componentProperties.toConfiguration();
297    
298                            CompositeConfiguration compositeConfiguration =
299                                    classLoaderAggregateProperties.getBaseConfiguration();
300    
301                            Field field2 = CompositeConfiguration.class.getDeclaredField(
302                                    "configList");
303    
304                            field2.setAccessible(true);
305    
306                            @SuppressWarnings("unchecked")
307                            List<Configuration> configurations =
308                                    (List<Configuration>)field2.get(compositeConfiguration);
309    
310                            Iterator<Configuration> itr = configurations.iterator();
311    
312                            while (itr.hasNext()) {
313                                    Configuration configuration = itr.next();
314    
315                                    if (!(configuration instanceof MapConfiguration)) {
316                                            break;
317                                    }
318    
319                                    MapConfiguration mapConfiguration =
320                                            (MapConfiguration)configuration;
321    
322                                    if (mapConfiguration.getMap() == properties) {
323                                            itr.remove();
324    
325                                            classLoaderAggregateProperties.removeConfiguration(
326                                                    configuration);
327                                    }
328                            }
329    
330                            clearCache();
331                    }
332                    catch (Exception e) {
333                            _log.error("The properties could not be removed", e);
334                    }
335            }
336    
337            @Override
338            public void set(String key, String value) {
339                    ComponentProperties componentProperties = getComponentProperties();
340    
341                    componentProperties.setProperty(key, value);
342    
343                    _values.put(key, value);
344            }
345    
346            protected String buildFilterCacheKey(
347                    String key, Filter filter, boolean arrayValue) {
348    
349                    if (filter.getVariables() != null) {
350                            return null;
351                    }
352    
353                    String[] selectors = filter.getSelectors();
354    
355                    int length = 0;
356    
357                    if (arrayValue) {
358                            length = selectors.length + 2;
359                    }
360                    else {
361                            length = selectors.length + 1;
362                    }
363    
364                    StringBundler sb = new StringBundler(length);
365    
366                    if (arrayValue) {
367                            sb.append(_ARRAY_KEY_PREFIX);
368                    }
369    
370                    sb.append(key);
371                    sb.append(selectors);
372    
373                    return sb.toString();
374            }
375    
376            protected Object fixArrayValue(String cacheKey, String[] array) {
377                    if (cacheKey == null) {
378                            return array;
379                    }
380    
381                    Object value = _nullValue;
382    
383                    if (ArrayUtil.isNotEmpty(array)) {
384    
385                            // Commons Configuration parses an empty property into a String
386                            // array with one String containing one space. It also leaves a
387                            // trailing array member if you set a property in more than one
388                            // line.
389    
390                            if (Validator.isNull(array[array.length - 1])) {
391                                    String[] subArray = new String[array.length - 1];
392    
393                                    System.arraycopy(array, 0, subArray, 0, subArray.length);
394    
395                                    array = subArray;
396                            }
397    
398                            if (array.length > 0) {
399                                    value = array;
400                            }
401                    }
402    
403                    _values.put(cacheKey, value);
404    
405                    return value;
406            }
407    
408            protected ComponentProperties getComponentProperties() {
409                    return _componentConfiguration.getProperties();
410            }
411    
412            protected com.germinus.easyconf.Filter getEasyConfFilter(Filter filter) {
413                    com.germinus.easyconf.Filter easyConfFilter =
414                            com.germinus.easyconf.Filter.by(filter.getSelectors());
415    
416                    if (filter.getVariables() != null) {
417                            easyConfFilter.setVariables(filter.getVariables());
418                    }
419    
420                    return easyConfFilter;
421            }
422    
423            protected void printSources(long companyId, String webId) {
424                    ComponentProperties componentProperties = getComponentProperties();
425    
426                    List<String> sources = componentProperties.getLoadedSources();
427    
428                    for (int i = sources.size() - 1; i >= 0; i--) {
429                            String source = sources.get(i);
430    
431                            if (_printedSources.contains(source)) {
432                                    continue;
433                            }
434    
435                            _printedSources.add(source);
436    
437                            String info = "Loading " + source;
438    
439                            if (companyId > CompanyConstants.SYSTEM) {
440                                    info +=
441                                            " for {companyId=" + companyId + ", webId=" + webId + "}";
442                            }
443    
444                            System.out.println(info);
445                    }
446            }
447    
448            private static final String _ARRAY_KEY_PREFIX = "ARRAY_";
449    
450            private static final boolean _PRINT_DUPLICATE_CALLS_TO_GET = false;
451    
452            private static Log _log = LogFactoryUtil.getLog(ConfigurationImpl.class);
453    
454            private static String[] _emptyArray = new String[0];
455            private static Object _nullValue = new Object();
456    
457            private ComponentConfiguration _componentConfiguration;
458            private Set<String> _printedSources = new HashSet<String>();
459            private Map<String, Object> _values =
460                    new ConcurrentHashMap<String, Object>();
461    
462    }