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.activemq.plugin; 018 019import org.apache.activemq.util.IntrospectionSupport; 020import org.slf4j.Logger; 021import org.slf4j.LoggerFactory; 022 023import javax.annotation.PostConstruct; 024import javax.xml.bind.JAXBElement; 025import java.lang.reflect.Method; 026import java.util.ArrayList; 027import java.util.LinkedList; 028import java.util.List; 029import java.util.Properties; 030import java.util.regex.Pattern; 031import org.apache.activemq.schema.core.DtoBroker; 032 033public class DefaultConfigurationProcessor implements ConfigurationProcessor { 034 035 public static final Logger LOG = LoggerFactory.getLogger(DefaultConfigurationProcessor.class); 036 RuntimeConfigurationBroker plugin; 037 Class configurationClass; 038 039 Pattern matchPassword = Pattern.compile("password=.*,"); 040 041 public DefaultConfigurationProcessor(RuntimeConfigurationBroker plugin, Class configurationClass) { 042 this.plugin = plugin; 043 this.configurationClass = configurationClass; 044 } 045 046 @Override 047 public void processChanges(DtoBroker currentConfiguration, DtoBroker modifiedConfiguration) { 048 List current = filter(currentConfiguration, configurationClass); 049 List modified = filter(modifiedConfiguration, configurationClass); 050 051 if (current.equals(modified)) { 052 plugin.debug("no changes to " + configurationClass.getSimpleName()); 053 return; 054 } else { 055 plugin.info("changes to " + configurationClass.getSimpleName()); 056 } 057 058 processChanges(current, modified); 059 } 060 061 public void processChanges(List current, List modified) { 062 int modIndex = 0, currentIndex = 0; 063 for (; modIndex < modified.size() && currentIndex < current.size(); modIndex++, currentIndex++) { 064 // walk the list for mods 065 applyModifications(getContents(current.get(currentIndex)), 066 getContents(modified.get(modIndex))); 067 } 068 069 for (; modIndex < modified.size(); modIndex++) { 070 // new element; add all 071 for (Object nc : getContents(modified.get(modIndex))) { 072 ConfigurationProcessor processor = findProcessor(nc); 073 if (processor != null) { 074 processor.addNew(nc); 075 } else { 076 addNew(nc); 077 } 078 } 079 } 080 081 for (; currentIndex < current.size(); currentIndex++) { 082 // removal of element; remove all 083 for (Object nc : getContents(current.get(currentIndex))) { 084 ConfigurationProcessor processor = findProcessor(nc); 085 if (processor != null) { 086 processor.remove(nc); 087 } else { 088 remove(nc); 089 } 090 } 091 } 092 } 093 094 protected void applyModifications(List<Object> current, List<Object> modification) { 095 int modIndex = 0, currentIndex = 0; 096 for (; modIndex < modification.size() && currentIndex < current.size(); modIndex++, currentIndex++) { 097 Object existing = current.get(currentIndex); 098 Object candidate = modification.get(modIndex); 099 if (!existing.equals(candidate)) { 100 plugin.debug("modification to:" + existing + " , with: " + candidate); 101 ConfigurationProcessor processor = findProcessor(existing); 102 if (processor != null) { 103 processor.modify(existing, candidate); 104 } else { 105 modify(existing, candidate); 106 } 107 } 108 } 109 for (; modIndex < modification.size(); modIndex++) { 110 Object mod = modification.get(modIndex); 111 ConfigurationProcessor processor = findProcessor(mod); 112 if (processor != null) { 113 processor.addNew(mod); 114 } else { 115 addNew(mod); 116 } 117 } 118 for (; currentIndex < current.size(); currentIndex++) { 119 Object mod = current.get(currentIndex); 120 ConfigurationProcessor processor = findProcessor(mod); 121 if (processor != null) { 122 processor.remove(mod); 123 } else { 124 remove(mod); 125 } 126 } 127 } 128 129 public void modify(Object existing, Object candidate) { 130 remove(existing); 131 addNew(candidate); 132 } 133 134 public void addNew(Object o) { 135 plugin.info("No runtime support for additions of " + o); 136 } 137 138 public void remove(Object o) { 139 plugin.info("No runtime support for removal of: " + o); 140 } 141 142 @Override 143 public ConfigurationProcessor findProcessor(Object o) { 144 plugin.info("No processor for " + o); 145 return null; 146 } 147 148 // mapping all supported updatable elements to support getContents 149 protected List<Object> getContents(Object o) { 150 List<Object> answer = new ArrayList<Object>(); 151 try { 152 Object val = o.getClass().getMethod("getContents", new Class[]{}).invoke(o, new Object[]{}); 153 if (val instanceof List) { 154 answer = (List<Object>) val; 155 } else { 156 answer.add(val); 157 } 158 } catch (NoSuchMethodException mappingIncomplete) { 159 plugin.debug(filterPasswords(o) + " has no modifiable elements"); 160 } catch (Exception e) { 161 plugin.info("Failed to access getContents for " + o + ", runtime modifications not supported", e); 162 } 163 return answer; 164 } 165 166 protected String filterPasswords(Object toEscape) { 167 return matchPassword.matcher(toEscape.toString()).replaceAll("password=???,"); 168 } 169 170 protected <T> List<Object> filter(Object obj, Class<T> type) { 171 return filter(getContents(obj), type); 172 } 173 174 protected <T> List<Object> filter(List<Object> objectList, Class<T> type) { 175 List<Object> result = new LinkedList<Object>(); 176 for (Object o : objectList) { 177 if (o instanceof JAXBElement) { 178 JAXBElement element = (JAXBElement) o; 179 if (type.isAssignableFrom(element.getDeclaredType())) { 180 result.add((T) element.getValue()); 181 } 182 } else if (type.isAssignableFrom(o.getClass())) { 183 result.add((T) o); 184 } 185 } 186 return result; 187 } 188 189 protected <T> T fromDto(Object dto, T instance) { 190 Properties properties = new Properties(); 191 IntrospectionSupport.getProperties(dto, properties, null); 192 plugin.placeHolderUtil.filter(properties); 193 LOG.trace("applying props: " + filterPasswords(properties) + ", to " + instance.getClass().getSimpleName()); 194 IntrospectionSupport.setProperties(instance, properties); 195 196 // deal with nested elements 197 for (Object nested : filter(dto, Object.class)) { 198 String elementName = nested.getClass().getSimpleName(); 199 Method setter = JAXBUtils.findSetter(instance, elementName); 200 if (setter != null) { 201 List<Object> argument = new LinkedList<Object>(); 202 for (Object elementContent : filter(nested, Object.class)) { 203 argument.add(fromDto(elementContent, JAXBUtils.inferTargetObject(elementContent))); 204 } 205 try { 206 setter.invoke(instance, JAXBUtils.matchType(argument, setter.getParameterTypes()[0])); 207 } catch (Exception e) { 208 plugin.info("failed to invoke " + setter + " on " + instance + " with args " + argument, e); 209 } 210 } else { 211 plugin.info("failed to find setter for " + elementName + " on :" + instance); 212 } 213 } 214 invokePostConstruct(instance); 215 return instance; 216 } 217 218 private <T> void invokePostConstruct(T instance) { 219 try { 220 for (Method m : instance.getClass().getDeclaredMethods()) { 221 if (m.isAnnotationPresent(PostConstruct.class) && m.getParameterCount() == 0) { 222 try { 223 JAXBUtils.ensureAccessible(m); 224 m.invoke(instance, null); 225 } catch (Exception e) { 226 plugin.info("failed to invoke @PostConstruct method " + m + " on " + instance, e); 227 } 228 } 229 } 230 } catch (Exception ignored) {} 231 } 232}