001package org.javasimon; 002 003import java.io.Reader; 004import java.lang.reflect.InvocationTargetException; 005import java.util.ArrayList; 006import java.util.LinkedHashMap; 007import java.util.List; 008import java.util.Map; 009import javax.xml.stream.XMLInputFactory; 010import javax.xml.stream.XMLStreamException; 011import javax.xml.stream.XMLStreamReader; 012 013import org.javasimon.callback.Callback; 014import org.javasimon.callback.CompositeCallback; 015import org.javasimon.callback.CompositeCallbackImpl; 016import org.javasimon.callback.CompositeFilterCallback; 017import org.javasimon.callback.FilterCallback; 018import org.javasimon.callback.FilterRule; 019import org.javasimon.utils.bean.SimonBeanUtils; 020 021/** 022 * Holds configuration for one Simon Manager. Configuration is read from the stream 023 * and it is merged with any existing configuration read before. Method {@link #clear()} 024 * must be used in order to reset this configuration object. 025 * <p> 026 * Every {@link org.javasimon.Manager} holds its own configuration and programmer has 027 * to take care of the initialization of the configuration. Default {@link org.javasimon.SimonManager} 028 * is privileged and can be configured via file or resource when Java property {@code javasimon.config.file} 029 * (constant {@link org.javasimon.SimonManager#PROPERTY_CONFIG_FILE_NAME}) 030 * or {@code javasimon.config.resource} (constant 031 * {@link org.javasimon.SimonManager#PROPERTY_CONFIG_RESOURCE_NAME}) is used. 032 * <p> 033 * <b>Structure of the configuration XML:</b> 034 * <pre>{@code 035 * <simon-configuration> 036 * ... TODO 037 * </simon-configuration>}</pre> 038 * 039 * @author <a href="mailto:virgo47@gmail.com">Richard "Virgo" Richter</a> 040 */ 041// TODO: This class needs serious rethinking, also manager config itself should be independent from the reading 042// of the config - so it can be set up by Spring for instance 043public final class ManagerConfiguration { 044 045 private Map<SimonPattern, SimonConfiguration> configs; 046 047 private final Manager manager; 048 049 /** 050 * Creates manager configuration for a specified manager. 051 * 052 * @param manager manager on whose behalf this configuration is created 053 */ 054 ManagerConfiguration(Manager manager) { 055 this.manager = manager; 056 clear(); 057 } 058 059 /** Clears any previously loaded configuration. */ 060 public void clear() { 061 configs = new LinkedHashMap<>(); 062 } 063 064 /** 065 * Reads config from provided buffered reader. Reader is not closed after this method finishes. 066 * 067 * @param reader reader containing configuration 068 */ 069 public synchronized void readConfig(Reader reader) { 070 try { 071 XMLStreamReader xr = XMLInputFactory.newInstance().createXMLStreamReader(reader); 072 try { 073 while (!xr.isStartElement()) { 074 xr.next(); 075 } 076 processStartElement(xr, "simon-configuration"); 077 while (true) { 078 if (isStartTag(xr, "callback")) { 079 manager.callback().addCallback(processCallback(xr)); 080 } else if (isStartTag(xr, "filter-callback")) { 081 manager.callback().addCallback(processFilterCallback(xr)); 082 } else if (isStartTag(xr, "simon")) { 083 processSimon(xr); 084 } else { 085 break; 086 } 087 } 088 assertEndTag(xr, "simon-configuration"); 089 } finally { 090 xr.close(); 091 } 092 } catch (XMLStreamException e) { 093 manager.callback().onManagerWarning(null, e); 094 } catch (SimonException e) { 095 manager.callback().onManagerWarning(e.getMessage(), e); 096 } 097 } 098 099 private Callback processCallback(XMLStreamReader xr) throws XMLStreamException { 100 Map<String, String> attrs = processStartElement(xr, "callback"); 101 String klass = attrs.get("class"); 102 if (klass == null) { 103 klass = CompositeCallbackImpl.class.getName(); 104 } 105 Callback callback; 106 try { 107 callback = (Callback) Class.forName(klass).newInstance(); 108 } catch (InstantiationException | ClassCastException | ClassNotFoundException | IllegalAccessException e) { 109 throw new SimonException(e); 110 } 111 112 processSetAndCallbacks(xr, callback); 113 processEndElement(xr, "callback"); 114 return callback; 115 } 116 117 private Callback processFilterCallback(XMLStreamReader xr) throws XMLStreamException { 118 Map<String, String> attrs = processStartElement(xr, "filter-callback"); 119 String klass = attrs.get("class"); 120 if (klass == null) { 121 klass = CompositeFilterCallback.class.getName(); 122 } 123 FilterCallback callback; 124 try { 125 callback = (FilterCallback) Class.forName(klass).newInstance(); 126 } catch (InstantiationException | ClassCastException | ClassNotFoundException | IllegalAccessException e) { 127 throw new SimonException(e); 128 } 129 130 while (isStartTag(xr, "rule")) { 131 processRule(xr, callback); 132 } 133 processSetAndCallbacks(xr, callback); 134 processEndElement(xr, "filter-callback"); 135 return callback; 136 } 137 138 private void processSetAndCallbacks(XMLStreamReader xr, Callback callback) throws XMLStreamException { 139 while (isStartTag(xr, "set")) { 140 processSet(xr, callback); 141 } 142 while (true) { 143 if (isStartTag(xr, "callback")) { 144 ((CompositeCallback) callback).addCallback(processCallback(xr)); 145 } else if (isStartTag(xr, "filter-callback")) { 146 ((CompositeCallback) callback).addCallback(processFilterCallback(xr)); 147 } else { 148 break; 149 } 150 } 151 } 152 153 private void processRule(XMLStreamReader xr, FilterCallback callback) throws XMLStreamException { 154 String pattern = null; 155 FilterRule.Type type = FilterRule.Type.SUFFICE; 156 String condition = null; 157 List<Callback.Event> events = new ArrayList<>(); 158 159 Map<String, String> attrs = processStartElement(xr, "rule"); 160 if (attrs.get("condition") != null) { 161 condition = attrs.get("condition"); 162 } 163 if (attrs.get("type") != null) { 164 type = FilterRule.Type.valueOf(toEnum(attrs.get("type"))); 165 } 166 if (attrs.get("pattern") != null) { 167 pattern = attrs.get("pattern"); 168 } 169 if (attrs.get("events") != null) { 170 String[] sa = attrs.get("events").trim().split(" *, *"); 171 for (String eventName : sa) { 172 events.add(Callback.Event.forCode(eventName)); 173 } 174 } 175 if (isStartTag(xr, "condition")) { 176 xr.next(); 177 condition = getText(xr); 178 processEndElement(xr, "condition"); 179 } 180 processEndElement(xr, "rule"); 181 callback.addRule(type, condition, pattern, events.toArray(new Callback.Event[events.size()])); 182 } 183 184 private void processSet(XMLStreamReader xr, Callback callback) throws XMLStreamException { 185 Map<String, String> attrs = processStartElement(xr, "set", "property"); 186 setProperty(callback, attrs.get("property"), attrs.get("value")); 187 processEndElement(xr, "set"); 188 } 189 190 /** 191 * Sets the callback property. 192 * 193 * @param callback callback object 194 * @param property name of the property 195 * @param value value of the property 196 */ 197 private void setProperty(Callback callback, String property, String value) { 198 try { 199 if (value != null) { 200 SimonBeanUtils.getInstance().setProperty(callback, property, value); 201 } else { 202 callback.getClass().getMethod(setterName(property)).invoke(callback); 203 } 204 } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { 205 throw new SimonException(e); 206 } 207 } 208 209 private String setterName(String name) { 210 return "set" + name.substring(0, 1).toUpperCase() + name.substring(1); 211 } 212 213 private void processSimon(XMLStreamReader xr) throws XMLStreamException { 214 Map<String, String> attrs = processStartElement(xr, "simon", "pattern"); 215 String pattern = attrs.get("pattern"); 216 SimonState state = attrs.get("state") != null ? SimonState.valueOf(toEnum(attrs.get("state"))) : null; 217 configs.put(new SimonPattern(pattern), new SimonConfiguration(state)); 218 processEndElement(xr, "simon"); 219 } 220 221 /** 222 * Returns configuration for the Simon with the specified name. 223 * 224 * @param name Simon name 225 * @return configuration for that particular Simon 226 */ 227 synchronized SimonConfiguration getConfig(String name) { 228 SimonState state = null; 229 230 for (Map.Entry<SimonPattern, SimonConfiguration> entry : configs.entrySet()) { 231 if (entry.getKey().matches(name)) { 232 SimonConfiguration config = entry.getValue(); 233 if (config.getState() != null) { 234 state = config.getState(); 235 } 236 } 237 } 238 return new SimonConfiguration(state); 239 } 240 241 private String toEnum(String enumVal) { 242 return enumVal.trim().toUpperCase().replace('-', '_'); 243 } 244 245 // XML Utils 246 247 private Map<String, String> processStartElement(XMLStreamReader reader, String elementName, String... requiredAttributes) throws XMLStreamException { 248 Map<String, String> attrs = processStartElementPrivate(reader, elementName, requiredAttributes); 249 reader.nextTag(); 250 return attrs; 251 } 252 253 private Map<String, String> processStartElementPrivate(XMLStreamReader reader, String elementName, String... requiredAttributes) throws XMLStreamException { 254 assertStartTag(reader, elementName); 255 Map<String, String> attrs = readAttributes(reader); 256 for (String attr : requiredAttributes) { 257 if (!attrs.containsKey(attr)) { 258 throw new XMLStreamException("Attribute '" + attr + "' MUST be present (element: " + elementName + "). " + readerPosition(reader)); 259 } 260 } 261 return attrs; 262 } 263 264 private void assertStartTag(XMLStreamReader reader, String name) throws XMLStreamException { 265 if (!reader.isStartElement()) { 266 throw new XMLStreamException("Assert start tag - wrong event type " + reader.getEventType() + " (expected name: " + name + ") " + readerPosition(reader)); 267 } 268 assertName(reader, "start tag", name); 269 } 270 271 private Map<String, String> readAttributes(XMLStreamReader reader) { 272 Map<String, String> attributes = new LinkedHashMap<>(); 273 int attrCount = reader.getAttributeCount(); 274 for (int i = 0; i < attrCount; i++) { 275 attributes.put(reader.getAttributeName(i).toString(), reader.getAttributeValue(i)); 276 } 277 return attributes; 278 } 279 280 private void assertName(XMLStreamReader reader, String operation, String name) throws XMLStreamException { 281 if (!reader.getLocalName().equals(name)) { 282 throw new XMLStreamException("Assert " + operation + " - wrong element name " + reader.getName().toString() + " (expected name: " + name + ") " + readerPosition(reader)); 283 } 284 } 285 286 private String readerPosition(XMLStreamReader reader) { 287 return "[line: " + reader.getLocation().getLineNumber() + ", column: " + reader.getLocation().getColumnNumber() + "]"; 288 } 289 290 private void assertEndTag(XMLStreamReader reader, String name) throws XMLStreamException { 291 if (!reader.isEndElement()) { 292 throw new XMLStreamException("Assert end tag - wrong event type " + reader.getEventType() + " (expected name: " + name + ") " + readerPosition(reader)); 293 } 294 assertName(reader, "end tag", name); 295 } 296 297 private boolean isStartTag(XMLStreamReader reader, String name) { 298 return reader.isStartElement() && reader.getLocalName().equals(name); 299 } 300 301 private void processEndElement(XMLStreamReader reader, String name) throws XMLStreamException { 302 assertEndTag(reader, name); 303 reader.nextTag(); 304 } 305 306 private String getText(XMLStreamReader reader) throws XMLStreamException { 307 StringBuilder sb = new StringBuilder(); 308 while (reader.isCharacters()) { 309 sb.append(reader.getText()); 310 reader.next(); 311 } 312 return sb.toString().trim(); 313 } 314}