001/* 002 * (C) Copyright 2015-2017 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Andre Justo 018 * Anahide Tchertchian 019 * Kevin Leturc <[email protected]> 020 */ 021package org.nuxeo.runtime.services.config; 022 023import java.io.IOException; 024import java.io.Serializable; 025import java.util.List; 026import java.util.Map; 027import java.util.Properties; 028import java.util.stream.Collectors; 029 030import com.fasterxml.jackson.dataformat.javaprop.JavaPropsMapper; 031import com.fasterxml.jackson.databind.node.ObjectNode; 032import org.apache.commons.lang3.StringUtils; 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.nuxeo.runtime.api.Framework; 036import org.nuxeo.runtime.logging.DeprecationLogger; 037import org.nuxeo.runtime.model.ComponentInstance; 038import org.nuxeo.runtime.model.DefaultComponent; 039 040import com.fasterxml.jackson.databind.ObjectMapper; 041 042/** 043 * @since 7.4 044 */ 045public class ConfigurationServiceImpl extends DefaultComponent implements ConfigurationService { 046 047 protected static final Log log = LogFactory.getLog(ConfigurationServiceImpl.class); 048 049 public static final String CONFIGURATION_EP = "configuration"; 050 051 protected static final JavaPropsMapper PROPERTIES_MAPPER = new JavaPropsMapper(); 052 protected static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 053 054 /** 055 * XXX remove once we are able to get such a cached map from DefaultComponent 056 * 057 * @since 10.3 058 */ 059 protected volatile Map<String, ConfigurationPropertyDescriptor> descriptors; 060 061 /** 062 * XXX remove once we are able to get such a cached map from DefaultComponent. 063 * <p> 064 * We'd ideally need a <T extends Descriptor> Map<String, T> getDescriptors(String xp) with cache method. 065 * 066 * @since 10.3 067 */ 068 protected Map<String, ConfigurationPropertyDescriptor> getDescriptors() { 069 Map<String, ConfigurationPropertyDescriptor> d = descriptors; 070 if (d == null) { 071 synchronized (this) { 072 d = descriptors; 073 if (d == null) { 074 List<ConfigurationPropertyDescriptor> descs = getDescriptors(CONFIGURATION_EP); 075 descriptors = d = descs.stream().collect(Collectors.toMap(desc -> desc.getId(), desc -> desc)); 076 } 077 } 078 } 079 return d; 080 } 081 082 @Override 083 public String getProperty(String key) { 084 return getProperty(key, null); 085 } 086 087 @Override 088 public String getProperty(String key, String defaultValue) { 089 ConfigurationPropertyDescriptor conf = getDescriptors().get(key); 090 if (conf == null) { 091 return defaultValue; 092 } 093 String value = conf.getValue(); 094 return value != null ? value : defaultValue; 095 } 096 097 @Override 098 public boolean isBooleanPropertyTrue(String key) { 099 String value = getProperty(key); 100 return Boolean.parseBoolean(value); 101 } 102 103 @Override 104 public boolean isBooleanPropertyFalse(String key) { 105 String value = getProperty(key); 106 return StringUtils.isNotBlank(value) && !Boolean.parseBoolean(value); 107 } 108 109 @Override 110 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 111 if (CONFIGURATION_EP.equals(extensionPoint)) { 112 synchronized (this) { 113 descriptors = null; 114 } 115 ConfigurationPropertyDescriptor configurationPropertyDescriptor = (ConfigurationPropertyDescriptor) contribution; 116 String key = configurationPropertyDescriptor.getName(); 117 if (Framework.getProperties().containsKey(key)) { 118 String message = "Property '" + key + "' should now be contributed to extension " 119 + "point 'org.nuxeo.runtime.ConfigurationService', using target 'configuration'"; 120 DeprecationLogger.log(message, "7.4"); 121 Framework.getRuntime().getMessageHandler().addWarning(message); 122 } 123 super.registerContribution(contribution, extensionPoint, contributor); 124 } 125 } 126 127 @Override 128 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 129 if (CONFIGURATION_EP.equals(extensionPoint)) { 130 synchronized (this) { 131 descriptors = null; 132 } 133 super.unregisterContribution(contribution, extensionPoint, contributor); 134 } 135 } 136 137 @Override 138 public Map<String, Serializable> getProperties(String namespace) { 139 if (StringUtils.isEmpty(namespace)) { 140 return null; 141 } 142 if (namespace.charAt(namespace.length() - 1) == '.') { 143 throw new IllegalArgumentException("namespace cannot end with a dot"); 144 } 145 return getDescriptors().values() 146 .stream() 147 .filter(desc -> startsWithNamespace(desc.getName(), namespace)) 148 .collect(Collectors.toMap(desc -> desc.getId().substring(namespace.length() + 1), 149 desc -> desc.getValue() != null && desc.list 150 ? desc.getValue().split(LIST_SEPARATOR) : desc.getValue())); 151 } 152 153 @Override 154 public String getPropertiesAsJson(String namespace) throws IOException { 155 // Build properties with indexes for lists 156 Properties properties = new Properties(); 157 getProperties(namespace).forEach((key, value) -> { 158 if (value instanceof String[]) { 159 int idx = 1; 160 for (String v : (String[]) value) { 161 properties.put(String.format("%s.%d", key, idx++), v); 162 } 163 } else { 164 properties.put(key, value); 165 } 166 }); 167 return OBJECT_MAPPER.writer().writeValueAsString( 168 PROPERTIES_MAPPER.readPropertiesAs(properties, ObjectNode.class)); 169 } 170 171 /** 172 * Returns true if a string starts with a namespace. 173 * 174 * @param string a string 175 * @param namespace 176 * @since 10.3 177 */ 178 protected static boolean startsWithNamespace(String string, String namespace) { 179 int nl = namespace.length(); 180 return string.length() > nl && string.charAt(nl) == '.' && string.startsWith(namespace); 181 } 182 183}