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