001    /**
002     * Copyright (c) 2000-2012 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.AggregatedProperties;
018    import com.germinus.easyconf.ComponentConfiguration;
019    import com.germinus.easyconf.ComponentProperties;
020    import com.germinus.easyconf.Conventions;
021    import com.germinus.easyconf.EasyConf;
022    
023    import com.liferay.portal.kernel.configuration.Filter;
024    import com.liferay.portal.kernel.log.Log;
025    import com.liferay.portal.kernel.log.LogFactoryUtil;
026    import com.liferay.portal.kernel.util.PropertiesUtil;
027    import com.liferay.portal.kernel.util.StreamUtil;
028    import com.liferay.portal.kernel.util.StringBundler;
029    import com.liferay.portal.kernel.util.StringPool;
030    import com.liferay.portal.kernel.util.StringUtil;
031    import com.liferay.portal.kernel.util.Validator;
032    import com.liferay.portal.model.Company;
033    import com.liferay.portal.model.CompanyConstants;
034    import com.liferay.portal.service.CompanyLocalServiceUtil;
035    
036    import java.io.File;
037    import java.io.FileWriter;
038    import java.io.InputStream;
039    import java.io.Writer;
040    
041    import java.lang.reflect.Field;
042    
043    import java.net.URI;
044    import java.net.URISyntaxException;
045    import java.net.URL;
046    
047    import java.util.HashSet;
048    import java.util.Iterator;
049    import java.util.List;
050    import java.util.Map;
051    import java.util.Properties;
052    import java.util.Set;
053    import java.util.concurrent.ConcurrentHashMap;
054    
055    import org.apache.commons.configuration.CompositeConfiguration;
056    import org.apache.commons.configuration.Configuration;
057    import org.apache.commons.configuration.MapConfiguration;
058    
059    /**
060     * @author Brian Wing Shun Chan
061     * @author Shuyang Zhou
062     */
063    public class ConfigurationImpl
064            implements com.liferay.portal.kernel.configuration.Configuration {
065    
066            public ConfigurationImpl(ClassLoader classLoader, String name) {
067                    this(classLoader, name, CompanyConstants.SYSTEM);
068            }
069    
070            public ConfigurationImpl(
071                    ClassLoader classLoader, String name, long companyId) {
072    
073                    updateBasePath(classLoader, name);
074    
075                    String webId = null;
076    
077                    if (companyId > CompanyConstants.SYSTEM) {
078                            try {
079                                    Company company = CompanyLocalServiceUtil.getCompanyById(
080                                            companyId);
081    
082                                    webId = company.getWebId();
083                            }
084                            catch (Exception e) {
085                                    _log.error(e, e);
086                            }
087                    }
088    
089                    EasyConf.refreshAll();
090    
091                    if (webId != null) {
092                            _componentConfiguration = EasyConf.getConfiguration(
093                                    webId, getFileName(classLoader, name));
094                    }
095                    else {
096                            _componentConfiguration = EasyConf.getConfiguration(
097                                    getFileName(classLoader, name));
098                    }
099    
100                    printSources(companyId, webId);
101            }
102    
103            public void addProperties(Properties properties) {
104                    try {
105                            ComponentProperties componentProperties =
106                                    _componentConfiguration.getProperties();
107    
108                            AggregatedProperties aggregatedProperties =
109                                    (AggregatedProperties)componentProperties.toConfiguration();
110    
111                            Field field1 = CompositeConfiguration.class.getDeclaredField(
112                                    "configList");
113    
114                            field1.setAccessible(true);
115    
116                            // Add to configList of base conf
117    
118                            List<Configuration> configurations =
119                                    (List<Configuration>)field1.get(aggregatedProperties);
120    
121                            MapConfiguration newConfiguration = new MapConfiguration(
122                                    properties);
123    
124                            configurations.add(0, newConfiguration);
125    
126                            // Add to configList of AggregatedProperties itself
127    
128                            Class<?> clazz = aggregatedProperties.getClass();
129    
130                            Field field2 = clazz.getDeclaredField("baseConf");
131    
132                            field2.setAccessible(true);
133    
134                            CompositeConfiguration compositeConfiguration =
135                                    (CompositeConfiguration)field2.get(aggregatedProperties);
136    
137                            configurations = (List<Configuration>)field1.get(
138                                    compositeConfiguration);
139    
140                            configurations.add(0, newConfiguration);
141    
142                            clearCache();
143                    }
144                    catch (Exception e) {
145                            _log.error("The properties could not be added", e);
146                    }
147            }
148    
149            public void clearCache() {
150                    _values.clear();
151            }
152    
153            public boolean contains(String key) {
154                    Object value = _values.get(key);
155    
156                    if (value == null) {
157                            ComponentProperties componentProperties = getComponentProperties();
158    
159                            value = componentProperties.getProperty(key);
160    
161                            if (value == null) {
162                                    value = _nullValue;
163                            }
164    
165                            _values.put(key, value);
166                    }
167    
168                    if (value == _nullValue) {
169                            return false;
170                    }
171                    else {
172                            return true;
173                    }
174            }
175    
176            public String get(String key) {
177                    Object value = _values.get(key);
178    
179                    if (value == null) {
180                            ComponentProperties componentProperties = getComponentProperties();
181    
182                            value = componentProperties.getString(key);
183    
184                            if (value == null) {
185                                    value = _nullValue;
186                            }
187    
188                            _values.put(key, value);
189                    }
190                    else if (_PRINT_DUPLICATE_CALLS_TO_GET) {
191                            System.out.println("Duplicate call to get " + key);
192                    }
193    
194                    if (value instanceof String) {
195                            return (String)value;
196                    }
197                    else {
198                            return null;
199                    }
200            }
201    
202            public String get(String key, Filter filter) {
203                    String filterCacheKey = buildFilterCacheKey(key, filter, false);
204    
205                    Object value = null;
206    
207                    if (filterCacheKey != null) {
208                            value = _values.get(filterCacheKey);
209                    }
210    
211                    if (value == null) {
212                            ComponentProperties componentProperties = getComponentProperties();
213    
214                            value = componentProperties.getString(
215                                    key, getEasyConfFilter(filter));
216    
217                            if (filterCacheKey != null) {
218                                    if (value == null) {
219                                            value = _nullValue;
220                                    }
221    
222                                    _values.put(filterCacheKey, value);
223                            }
224                    }
225    
226                    if (value instanceof String) {
227                            return (String)value;
228                    }
229                    else {
230                            return null;
231                    }
232    
233            }
234    
235            public String[] getArray(String key) {
236                    String cacheKey = _ARRAY_KEY_PREFIX.concat(key);
237    
238                    Object value = _values.get(cacheKey);
239    
240                    if (value == null) {
241                            ComponentProperties componentProperties = getComponentProperties();
242    
243                            String[] array = componentProperties.getStringArray(key);
244    
245                            value = fixArrayValue(cacheKey, array);
246                    }
247    
248                    if (value instanceof String[]) {
249                            return (String[])value;
250                    }
251                    else {
252                            return _emptyArray;
253                    }
254            }
255    
256            public String[] getArray(String key, Filter filter) {
257                    String filterCacheKey = buildFilterCacheKey(key, filter, true);
258    
259                    Object value = null;
260    
261                    if (filterCacheKey != null) {
262                            value = _values.get(filterCacheKey);
263                    }
264    
265                    if (value == null) {
266                            ComponentProperties componentProperties = getComponentProperties();
267    
268                            String[] array = componentProperties.getStringArray(
269                                    key, getEasyConfFilter(filter));
270    
271                            value = fixArrayValue(filterCacheKey, array);
272                    }
273    
274                    if (value instanceof String[]) {
275                            return (String[])value;
276                    }
277                    else {
278                            return _emptyArray;
279                    }
280            }
281    
282            public Properties getProperties() {
283    
284                    // For some strange reason, componentProperties.getProperties() returns
285                    // values with spaces after commas. So a property setting of "xyz=1,2,3"
286                    // actually returns "xyz=1, 2, 3". This can break applications that
287                    // don't expect that extra space. However, getting the property value
288                    // directly through componentProperties returns the correct value. This
289                    // method fixes the weird behavior by returing properties with the
290                    // correct values.
291    
292                    Properties properties = new Properties();
293    
294                    ComponentProperties componentProperties = getComponentProperties();
295    
296                    Properties componentPropertiesProperties =
297                            componentProperties.getProperties();
298    
299                    for (Map.Entry<Object, Object> entry :
300                                    componentPropertiesProperties.entrySet()) {
301    
302                            String key = (String)entry.getKey();
303                            String value = (String)entry.getValue();
304    
305                            properties.setProperty(key, value);
306                    }
307    
308                    return properties;
309            }
310    
311            public Properties getProperties(String prefix, boolean removePrefix) {
312                    Properties properties = getProperties();
313    
314                    return PropertiesUtil.getProperties(properties, prefix, removePrefix);
315            }
316    
317            public void removeProperties(Properties properties) {
318                    try {
319                            ComponentProperties componentProperties =
320                                    _componentConfiguration.getProperties();
321    
322                            AggregatedProperties aggregatedProperties =
323                                    (AggregatedProperties)componentProperties.toConfiguration();
324    
325                            Class<?> clazz = aggregatedProperties.getClass();
326    
327                            Field field1 = clazz.getDeclaredField("baseConf");
328    
329                            field1.setAccessible(true);
330    
331                            CompositeConfiguration compositeConfiguration =
332                                    (CompositeConfiguration)field1.get(aggregatedProperties);
333    
334                            Field field2 = CompositeConfiguration.class.getDeclaredField(
335                                    "configList");
336    
337                            field2.setAccessible(true);
338    
339                            List<Configuration> configurations =
340                                    (List<Configuration>)field2.get(compositeConfiguration);
341    
342                            Iterator<Configuration> itr = configurations.iterator();
343    
344                            while (itr.hasNext()) {
345                                    Configuration configuration = itr.next();
346    
347                                    if (!(configuration instanceof MapConfiguration)) {
348                                            return;
349                                    }
350    
351                                    MapConfiguration mapConfiguration =
352                                            (MapConfiguration)configuration;
353    
354                                    if (mapConfiguration.getMap() == properties) {
355                                            itr.remove();
356    
357                                            aggregatedProperties.removeConfiguration(configuration);
358                                    }
359                            }
360    
361                            clearCache();
362                    }
363                    catch (Exception e) {
364                            _log.error("The properties could not be removed", e);
365                    }
366            }
367    
368            public void set(String key, String value) {
369                    ComponentProperties componentProperties = getComponentProperties();
370    
371                    componentProperties.setProperty(key, value);
372    
373                    _values.put(key, value);
374            }
375    
376            protected String buildFilterCacheKey(
377                    String key, Filter filter, boolean arrayValue) {
378    
379                    if (filter.getVariables() != null) {
380                            return null;
381                    }
382    
383                    String[] selectors = filter.getSelectors();
384    
385                    int length = 0;
386    
387                    if (arrayValue) {
388                            length = selectors.length + 2;
389                    }
390                    else {
391                            length = selectors.length + 1;
392                    }
393    
394                    StringBundler sb = new StringBundler(length);
395    
396                    if (arrayValue) {
397                            sb.append(_ARRAY_KEY_PREFIX);
398                    }
399    
400                    sb.append(key);
401                    sb.append(selectors);
402    
403                    return sb.toString();
404            }
405    
406            protected Object fixArrayValue(String cacheKey, String[] array) {
407                    if (cacheKey == null) {
408                            return array;
409                    }
410    
411                    Object value = _nullValue;
412    
413                    if ((array != null) && (array.length > 0)) {
414    
415                            // Commons Configuration parses an empty property into a String
416                            // array with one String containing one space. It also leaves a
417                            // trailing array member if you set a property in more than one
418                            // line.
419    
420                            if (Validator.isNull(array[array.length - 1])) {
421                                    String[] subArray = new String[array.length - 1];
422    
423                                    System.arraycopy(array, 0, subArray, 0, subArray.length);
424    
425                                    array = subArray;
426                            }
427    
428                            if (array.length > 0) {
429                                    value = array;
430                            }
431                    }
432    
433                    _values.put(cacheKey, value);
434    
435                    return value;
436            }
437    
438            protected ComponentProperties getComponentProperties() {
439                    return _componentConfiguration.getProperties();
440            }
441    
442            protected com.germinus.easyconf.Filter getEasyConfFilter(Filter filter) {
443                    com.germinus.easyconf.Filter easyConfFilter =
444                            com.germinus.easyconf.Filter.by(filter.getSelectors());
445    
446                    if (filter.getVariables() != null) {
447                            easyConfFilter.setVariables(filter.getVariables());
448                    }
449    
450                    return easyConfFilter;
451            }
452    
453            protected String getFileName(ClassLoader classLoader, String name) {
454                    URL url = classLoader.getResource(name + ".properties");
455    
456                    // If the resource is located inside of a JAR, then EasyConf needs the
457                    // "jar:file:" prefix appended to the path. Use URL.toExternalForm() to
458                    // achieve that. When running under JBoss, the protocol returned is
459                    // "vfs", "vfsfile", or "vfszip". When running under OC4J, the protocol
460                    // returned is "code-source". When running under WebLogic, the protocol
461                    // returned is "zip". When running under WebSphere, the protocol
462                    // returned is "wsjar".
463    
464                    String protocol = url.getProtocol();
465    
466                    if (protocol.equals("code-source") || protocol.equals("jar") ||
467                            protocol.equals("vfs") || protocol.equals("vfsfile") ||
468                            protocol.equals("vfszip") || protocol.equals("wsjar") ||
469                            protocol.equals("zip")) {
470    
471                            name = url.toExternalForm();
472                    }
473                    else {
474                            try {
475                                    name = new URI(url.getPath()).getPath();
476                            }
477                            catch (URISyntaxException urise) {
478                                    name = url.getFile();
479                            }
480                    }
481    
482                    int pos = name.lastIndexOf(".properties");
483    
484                    if (pos != -1) {
485                            name = name.substring(0, pos);
486                    }
487    
488                    return name;
489            }
490    
491            protected void printSources(long companyId, String webId) {
492                    ComponentProperties componentProperties = getComponentProperties();
493    
494                    List<String> sources = componentProperties.getLoadedSources();
495    
496                    for (int i = sources.size() - 1; i >= 0; i--) {
497                            String source = sources.get(i);
498    
499                            if (_printedSources.contains(source)) {
500                                    continue;
501                            }
502    
503                            _printedSources.add(source);
504    
505                            String info = "Loading " + source;
506    
507                            if (companyId > CompanyConstants.SYSTEM) {
508                                    info +=
509                                            " for {companyId=" + companyId + ", webId=" + webId + "}";
510                            }
511    
512                            System.out.println(info);
513                    }
514            }
515    
516            protected void updateBasePath(ClassLoader classLoader, String name) {
517                    InputStream inputStream = null;
518    
519                    try {
520                            URL url = classLoader.getResource(
521                                    name + Conventions.PROPERTIES_EXTENSION);
522    
523                            if (url == null) {
524                                    return;
525                            }
526    
527                            String protocol = url.getProtocol();
528    
529                            if (!protocol.equals("file")) {
530                                    return;
531                            }
532    
533                            Properties properties = new Properties();
534    
535                            inputStream = url.openStream();
536    
537                            properties.load(inputStream);
538    
539                            if (properties.containsKey("base.path")) {
540                                    return;
541                            }
542    
543                            String fileName = StringUtil.replace(
544                                    url.getFile(), "%20", StringPool.SPACE);
545    
546                            File file = new File(fileName);
547    
548                            if (!file.exists() || !file.canWrite()) {
549                                    if (_log.isWarnEnabled()) {
550                                            _log.warn("Unable to write " + file);
551                                    }
552    
553                                    return;
554                            }
555    
556                            Writer writer = new FileWriter(file, true);
557    
558                            StringBundler sb = new StringBundler(4);
559    
560                            sb.append(StringPool.OS_EOL);
561                            sb.append(StringPool.OS_EOL);
562                            sb.append("base.path=");
563    
564                            String basePath = url.getPath();
565    
566                            int pos = basePath.lastIndexOf(
567                                    StringPool.SLASH + name + Conventions.PROPERTIES_EXTENSION);
568    
569                            if (pos != -1) {
570                                    basePath = basePath.substring(0, pos);
571                            }
572    
573                            sb.append(basePath);
574    
575                            writer.write(sb.toString());
576    
577                            writer.close();
578                    }
579                    catch (Exception e) {
580                            _log.error(e, e);
581                    }
582                    finally {
583                            StreamUtil.cleanUp(inputStream);
584                    }
585            }
586    
587            private static final String _ARRAY_KEY_PREFIX = "ARRAY_";
588    
589            private static final boolean _PRINT_DUPLICATE_CALLS_TO_GET = false;
590    
591            private static Log _log = LogFactoryUtil.getLog(ConfigurationImpl.class);
592    
593            private static String[] _emptyArray = new String[0];
594            private static Object _nullValue = new Object();
595    
596            private ComponentConfiguration _componentConfiguration;
597            private Set<String> _printedSources = new HashSet<String>();
598            private Map<String, Object> _values =
599                    new ConcurrentHashMap<String, Object>();
600    
601    }