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