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