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 (Map.Entry<Object, Object> entry :
275                                    componentPropertiesProperties.entrySet()) {
276    
277                            String key = (String)entry.getKey();
278                            String value = (String)entry.getValue();
279    
280                            properties.setProperty(key, value);
281                    }
282    
283                    return properties;
284            }
285    
286            @Override
287            public Properties getProperties(String prefix, boolean removePrefix) {
288                    Properties properties = getProperties();
289    
290                    return PropertiesUtil.getProperties(properties, prefix, removePrefix);
291            }
292    
293            @Override
294            public void removeProperties(Properties properties) {
295                    try {
296                            ComponentProperties componentProperties =
297                                    _componentConfiguration.getProperties();
298    
299                            ClassLoaderAggregateProperties classLoaderAggregateProperties =
300                                    (ClassLoaderAggregateProperties)
301                                            componentProperties.toConfiguration();
302    
303                            CompositeConfiguration compositeConfiguration =
304                                    classLoaderAggregateProperties.getBaseConfiguration();
305    
306                            Field field2 = CompositeConfiguration.class.getDeclaredField(
307                                    "configList");
308    
309                            field2.setAccessible(true);
310    
311                            @SuppressWarnings("unchecked")
312                            List<Configuration> configurations =
313                                    (List<Configuration>)field2.get(compositeConfiguration);
314    
315                            Iterator<Configuration> itr = configurations.iterator();
316    
317                            while (itr.hasNext()) {
318                                    Configuration configuration = itr.next();
319    
320                                    if (!(configuration instanceof MapConfiguration)) {
321                                            break;
322                                    }
323    
324                                    MapConfiguration mapConfiguration =
325                                            (MapConfiguration)configuration;
326    
327                                    if (mapConfiguration.getMap() == properties) {
328                                            itr.remove();
329    
330                                            classLoaderAggregateProperties.removeConfiguration(
331                                                    configuration);
332                                    }
333                            }
334    
335                            clearCache();
336                    }
337                    catch (Exception e) {
338                            _log.error("The properties could not be removed", e);
339                    }
340            }
341    
342            @Override
343            public void set(String key, String value) {
344                    ComponentProperties componentProperties = getComponentProperties();
345    
346                    componentProperties.setProperty(key, value);
347    
348                    _values.put(key, value);
349            }
350    
351            protected String buildFilterCacheKey(
352                    String key, Filter filter, boolean arrayValue) {
353    
354                    if (filter.getVariables() != null) {
355                            return null;
356                    }
357    
358                    String[] selectors = filter.getSelectors();
359    
360                    int length = 0;
361    
362                    if (arrayValue) {
363                            length = selectors.length + 2;
364                    }
365                    else {
366                            length = selectors.length + 1;
367                    }
368    
369                    StringBundler sb = new StringBundler(length);
370    
371                    if (arrayValue) {
372                            sb.append(_ARRAY_KEY_PREFIX);
373                    }
374    
375                    sb.append(key);
376                    sb.append(selectors);
377    
378                    return sb.toString();
379            }
380    
381            protected Object fixArrayValue(String cacheKey, String[] array) {
382                    if (cacheKey == null) {
383                            return array;
384                    }
385    
386                    Object value = _nullValue;
387    
388                    if (ArrayUtil.isNotEmpty(array)) {
389    
390                            // Commons Configuration parses an empty property into a String
391                            // array with one String containing one space. It also leaves a
392                            // trailing array member if you set a property in more than one
393                            // line.
394    
395                            if (Validator.isNull(array[array.length - 1])) {
396                                    String[] subArray = new String[array.length - 1];
397    
398                                    System.arraycopy(array, 0, subArray, 0, subArray.length);
399    
400                                    array = subArray;
401                            }
402    
403                            if (array.length > 0) {
404                                    value = array;
405                            }
406                    }
407    
408                    _values.put(cacheKey, value);
409    
410                    return value;
411            }
412    
413            protected ComponentProperties getComponentProperties() {
414                    return _componentConfiguration.getProperties();
415            }
416    
417            protected com.germinus.easyconf.Filter getEasyConfFilter(Filter filter) {
418                    com.germinus.easyconf.Filter easyConfFilter =
419                            com.germinus.easyconf.Filter.by(filter.getSelectors());
420    
421                    if (filter.getVariables() != null) {
422                            easyConfFilter.setVariables(filter.getVariables());
423                    }
424    
425                    return easyConfFilter;
426            }
427    
428            protected void printSources(long companyId, String webId) {
429                    ComponentProperties componentProperties = getComponentProperties();
430    
431                    List<String> sources = componentProperties.getLoadedSources();
432    
433                    for (int i = sources.size() - 1; i >= 0; i--) {
434                            String source = sources.get(i);
435    
436                            if (_printedSources.contains(source)) {
437                                    continue;
438                            }
439    
440                            _printedSources.add(source);
441    
442                            String info = "Loading " + source;
443    
444                            if (companyId > CompanyConstants.SYSTEM) {
445                                    info +=
446                                            " for {companyId=" + companyId + ", webId=" + webId + "}";
447                            }
448    
449                            System.out.println(info);
450                    }
451            }
452    
453            private static final String _ARRAY_KEY_PREFIX = "ARRAY_";
454    
455            private static final boolean _PRINT_DUPLICATE_CALLS_TO_GET = false;
456    
457            private static Log _log = LogFactoryUtil.getLog(ConfigurationImpl.class);
458    
459            private static String[] _emptyArray = new String[0];
460            private static Object _nullValue = new Object();
461    
462            private ComponentConfiguration _componentConfiguration;
463            private Set<String> _printedSources = new HashSet<String>();
464            private Map<String, Object> _values =
465                    new ConcurrentHashMap<String, Object>();
466    
467    }