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}