001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.camel.blueprint; 018 019import java.lang.reflect.InvocationTargetException; 020import java.lang.reflect.Method; 021import java.util.ArrayList; 022import java.util.LinkedHashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Properties; 026import java.util.Set; 027 028import org.apache.aries.blueprint.ExtendedBeanMetadata; 029import org.apache.aries.blueprint.ext.AbstractPropertyPlaceholder; 030import org.apache.aries.blueprint.ext.PropertyPlaceholder; 031import org.apache.camel.component.properties.DefaultPropertiesParser; 032import org.apache.camel.component.properties.PropertiesComponent; 033import org.apache.camel.component.properties.PropertiesParser; 034import org.apache.camel.util.ObjectHelper; 035import org.osgi.service.blueprint.container.BlueprintContainer; 036import org.osgi.service.blueprint.reflect.ComponentMetadata; 037 038/** 039 * Blueprint {@link PropertiesParser} which supports looking up 040 * property placeholders from the Blueprint Property Placeholder Service. 041 * <p/> 042 * This implementation will sit on top of any existing configured 043 * {@link PropertiesParser} and will delegate to those in case Blueprint could not 044 * resolve the property. 045 */ 046public class BlueprintPropertiesParser extends DefaultPropertiesParser { 047 048 private final PropertiesComponent propertiesComponent; 049 private final BlueprintContainer container; 050 private final PropertiesParser delegate; 051 private final Set<PropertyPlaceholderWrapper> placeholders = new LinkedHashSet<PropertyPlaceholderWrapper>(); 052 private Method method; 053 private Method oldMethod; 054 055 public BlueprintPropertiesParser(PropertiesComponent propertiesComponent, BlueprintContainer container, PropertiesParser delegate) { 056 super(propertiesComponent); 057 this.propertiesComponent = propertiesComponent; 058 this.container = container; 059 this.delegate = delegate; 060 } 061 062 /** 063 * Lookup the ids of the Blueprint property placeholder services in the 064 * Blueprint container. 065 * 066 * @return the ids, will be an empty array if none found. 067 */ 068 public String[] lookupPropertyPlaceholderIds() { 069 List<String> ids = new ArrayList<String>(); 070 071 for (Object componentId : container.getComponentIds()) { 072 String id = (String) componentId; 073 ComponentMetadata meta = container.getComponentMetadata(id); 074 if (meta instanceof ExtendedBeanMetadata) { 075 Class<?> clazz = ((ExtendedBeanMetadata) meta).getRuntimeClass(); 076 if (clazz != null && (AbstractPropertyPlaceholder.class.isAssignableFrom(clazz) 077 || newPlaceholderClass(clazz) != null)) { 078 ids.add(id); 079 } 080 } 081 } 082 083 return ids.toArray(new String[ids.size()]); 084 } 085 086 /** 087 * Obtains a {@link Class} instance for "org.apache.aries.blueprint.ext.AbstractPropertyPlaceholderExt" 088 * @param clazz 089 * @return 090 */ 091 private Class<?> newPlaceholderClass(Class<?> clazz) { 092 Class<?> c = clazz; 093 while (c != null) { 094 if ("org.apache.aries.blueprint.ext.AbstractPropertyPlaceholderExt".equals(c.getName())) { 095 return c; 096 } 097 c = c.getSuperclass(); 098 } 099 return null; 100 } 101 102 /** 103 * Adds the given Blueprint property placeholder service with the given id 104 * 105 * @param id id of the Blueprint property placeholder service to add. 106 */ 107 public void addPropertyPlaceholder(String id) { 108 Object component = container.getComponentInstance(id); 109 110 // new API 111 if (component != null) { 112 Class<?> clazz = newPlaceholderClass(component.getClass()); 113 if (clazz != null) { 114 log.debug("Adding Blueprint PropertyPlaceholder: {}", id); 115 116 if (method == null) { 117 try { 118 method = clazz.getDeclaredMethod("retrieveValue", String.class); 119 method.setAccessible(true); 120 } catch (NoSuchMethodException e) { 121 throw new IllegalStateException("Cannot add blueprint property placeholder: " + id 122 + " as the method retrieveValue is not accessible", e); 123 } 124 } 125 placeholders.add(new PropertyPlaceholderWrapper(component, method)); 126 } 127 } 128 129 // old, deprecated API 130 if (component instanceof AbstractPropertyPlaceholder) { 131 AbstractPropertyPlaceholder placeholder = (AbstractPropertyPlaceholder) component; 132 133 log.debug("Adding Blueprint PropertyPlaceholder: {}", id); 134 135 if (oldMethod == null) { 136 try { 137 oldMethod = AbstractPropertyPlaceholder.class.getDeclaredMethod("retrieveValue", String.class); 138 oldMethod.setAccessible(true); 139 } catch (NoSuchMethodException e) { 140 throw new IllegalStateException("Cannot add blueprint property placeholder: " + id 141 + " as the method retrieveValue is not accessible", e); 142 } 143 } 144 placeholders.add(new PropertyPlaceholderWrapper(placeholder, oldMethod)); 145 } 146 } 147 148 @Override 149 public String parseProperty(String key, String value, Properties properties) { 150 log.trace("Parsing property key: {} with value: {}", key, value); 151 152 String answer = null; 153 154 // prefer any override properties 155 // this logic is special for BlueprintPropertiesParser as we otherwise prefer 156 // to use the AbstractPropertyPlaceholder from OSGi blueprint config admins 157 // service to lookup otherwise 158 if (key != null && propertiesComponent.getOverrideProperties() != null) { 159 answer = (String) propertiesComponent.getOverrideProperties().get(key); 160 } 161 162 // lookup key in blueprint and return its value 163 if (answer == null && key != null) { 164 for (PropertyPlaceholderWrapper placeholder : placeholders) { 165 boolean isDefault = false; 166 if (placeholders.size() > 1) { 167 // okay we have multiple placeholders and we want to return the answer that 168 // is not the default placeholder if there is multiple keys 169 Map map = placeholder.getDefaultProperties(); 170 isDefault = map != null && map.containsKey(key); 171 log.trace("Blueprint property key: {} is part of default properties: {}", key, isDefault); 172 } 173 174 try { 175 String candidate = placeholder.retrieveValue(key); 176 177 if (candidate != null) { 178 if (answer == null || !isDefault) { 179 log.trace("Blueprint parsed candidate property key: {} as value: {}", key, answer); 180 answer = candidate; 181 } 182 } 183 } catch (Exception ex) { 184 // Here we just catch the exception and try to use other candidate 185 } 186 } 187 log.debug("Blueprint parsed property key: {} as value: {}", key, answer); 188 } 189 190 // if there is a delegate then let it parse the current answer as it may be jasypt which 191 // need to decrypt values 192 if (delegate != null) { 193 String delegateAnswer = delegate.parseProperty(key, answer != null ? answer : value, properties); 194 if (delegateAnswer != null) { 195 answer = delegateAnswer; 196 log.debug("Delegate property parser parsed the property key: {} as value: {}", key, answer); 197 } 198 } 199 200 log.trace("Returning parsed property key: {} as value: {}", key, answer); 201 return answer; 202 } 203 204 private class PropertyPlaceholderWrapper { 205 206 private Object delegate; 207 private Method method; 208 209 public PropertyPlaceholderWrapper(Object delegate, Method method) { 210 this.delegate = delegate; 211 this.method = method; 212 } 213 214 public String retrieveValue(String key) { 215 Object v = ObjectHelper.invokeMethod(method, delegate, key); 216 return v == null ? null : v.toString(); 217 } 218 219 public Map getDefaultProperties() { 220 if (delegate instanceof PropertyPlaceholder) { 221 return ((PropertyPlaceholder) delegate).getDefaultProperties(); 222 } 223 try { 224 Method getDefaultProperties = delegate.getClass().getMethod("getDefaultProperties"); 225 return getDefaultProperties == null ? null : (Map) getDefaultProperties.invoke(delegate); 226 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 227 return null; 228 } 229 } 230 } 231 232}