001: package org.drools.agent;
002:
003: import java.io.IOException;
004: import java.io.InputStream;
005: import java.util.ArrayList;
006: import java.util.Arrays;
007: import java.util.Collections;
008: import java.util.Date;
009: import java.util.HashMap;
010: import java.util.Iterator;
011: import java.util.List;
012: import java.util.Map;
013: import java.util.Properties;
014: import java.util.StringTokenizer;
015: import java.util.Timer;
016: import java.util.TimerTask;
017:
018: import org.drools.RuleBase;
019: import org.drools.RuleBaseConfiguration;
020: import org.drools.RuleBaseFactory;
021: import org.drools.RuntimeDroolsException;
022: import org.drools.rule.Package;
023:
024: /**
025: * This manages a single rulebase, based on the properties given.
026: * You should only have ONE instance of this agent per rulebase configuration.
027: * You can get the rulebase from this agent repeatedly, as needed, or if you keep the rulebase,
028: * under most configurations it will be automatically updated.
029: *
030: * How this behaves depends on the properties that you pass into it (documented below)
031: *
032: * CONFIG OPTIONS (to be passed in as properties):
033: * <code>newInstance</code>: setting this to "true" means that each time the rules are changed
034: * a new instance of the rulebase is created (as opposed to updated in place)
035: * the default is to update in place. DEFAULT: false. If you set this to true,
036: * then you will need to call getRuleBase() each time you want to use it. If it is false,
037: * then it means you can keep your reference to the rulebase and it will be updated automatically
038: * (as well as any stateful sessions).
039: *
040: * <code>poll</code>The number of seconds to poll for changes. Polling
041: * happens in a background thread. eg: poll=30 #30 second polling.
042: *
043: * <code>file</code>: a space seperated listing of files that make up the
044: * packages of the rulebase. Each package can only be in one file. You can't have
045: * packages spread across files. eg: file=/your/dir/file1.pkg file=/your/dir/file2.pkg
046: *
047: * <code>dir</code>: a single file system directory to monitor for packages.
048: * As with files, each package must be in its own file.
049: * eg: dir=/your/dir
050: *
051: * <code>url</code>: A space seperated URL to a binary rulebase in the BRMS.
052: * eg: url=http://server/drools-jbrms/packages/somePakage/VERSION_1
053: * For URL you will also want a local cache directory setup:
054: * eg: localCacheDir=/some/dir/that/exists
055: * This is needed so that the runtime can startup and load packages even if the BRMS
056: * is not available (or the network).
057: *
058: * <code>name</code>
059: * the Name is used in any logging, so each agent can be differentiated (you may have one agent per rulebase
060: * that you need in your application).
061: *
062: * There is also an AgentEventListener interface which you can provide which will call back when lifecycle
063: * events happen, or errors/warnings occur. As the updating happens in a background thread, this may be important.
064: * The default event listener logs to the System.err output stream.
065: *
066: * @author Michael Neale
067: */
068: public class RuleAgent {
069:
070: /**
071: * Following are property keys to be used in the property
072: * config file.
073: */
074: public static final String NEW_INSTANCE = "newInstance";
075: public static final String FILES = "file";
076: public static final String DIRECTORY = "dir";
077: public static final String URLS = "url";
078: public static final String POLL_INTERVAL = "poll";
079: public static final String CONFIG_NAME = "name"; //name is optional
080:
081: //this is needed for cold starting when BRMS is down (ie only for URL).
082: public static final String LOCAL_URL_CACHE = "localCacheDir";
083:
084: /**
085: * Here is where we have a map of providers to the key that appears on the configuration.
086: */
087: public static Map PACKAGE_PROVIDERS = new HashMap() {
088: {
089: put(FILES, FileScanner.class);
090: put(DIRECTORY, DirectoryScanner.class);
091: put(URLS, URLScanner.class);
092: }
093: };
094:
095: /**
096: * This is true if the rulebase is created anew each time.
097: */
098: private boolean newInstance;
099:
100: /**
101: * The rule base that is being managed.
102: */
103: private RuleBase ruleBase;
104:
105: /**
106: * the configuration for the RuleBase
107: */
108: private RuleBaseConfiguration ruleBaseConf;
109:
110: /**
111: * The timer that is used to monitor for changes and deal with them.
112: */
113: private Timer timer;
114:
115: /**
116: * The providers that actually do the work.
117: */
118: List providers;
119:
120: /**
121: * This keeps the packages around that have been loaded.
122: */
123: Map packages = new HashMap();
124:
125: /**
126: * For logging events (important for stuff that happens in the background).
127: */
128: AgentEventListener listener = getDefaultListener();
129:
130: /**
131: * Polling interval value, in seconds, used in the Timer.
132: */
133: private int secondsToRefresh;
134:
135: /**
136: * Properties configured to load up packages into a rulebase (and monitor them
137: * for changes).
138: */
139: public static RuleAgent newRuleAgent(Properties config) {
140: return newRuleAgent(config, null, null);
141: }
142:
143: /**
144: * Properties configured to load up packages into a rulebase with the provided
145: * configuration (and monitor them for changes).
146: */
147: public static RuleAgent newRuleAgent(Properties config,
148: RuleBaseConfiguration ruleBaseConf) {
149: return newRuleAgent(config, null, ruleBaseConf);
150: }
151:
152: /**
153: * This allows an optional listener to be passed in.
154: * The default one prints some stuff out to System.err only when really needed.
155: */
156: public static RuleAgent newRuleAgent(Properties config,
157: AgentEventListener listener) {
158: return newRuleAgent(config, listener, null);
159: }
160:
161: /**
162: * This allows an optional listener to be passed in.
163: * The default one prints some stuff out to System.err only when really needed.
164: */
165: public static RuleAgent newRuleAgent(Properties config,
166: AgentEventListener listener,
167: RuleBaseConfiguration ruleBaseConf) {
168: RuleAgent agent = new RuleAgent(ruleBaseConf);
169: if (listener != null) {
170: agent.listener = listener;
171: }
172: agent.init(config);
173: return agent;
174: }
175:
176: void init(Properties config) {
177:
178: boolean newInstance = Boolean.valueOf(
179: config.getProperty(NEW_INSTANCE, "false"))
180: .booleanValue();
181: int secondsToRefresh = Integer.parseInt(config.getProperty(
182: POLL_INTERVAL, "-1"));
183: final String name = config.getProperty(CONFIG_NAME, "default");
184:
185: listener.setAgentName(name);
186:
187: listener.info("Configuring with newInstance=" + newInstance
188: + ", secondsToRefresh=" + secondsToRefresh);
189:
190: List provs = new ArrayList();
191:
192: for (Iterator iter = config.keySet().iterator(); iter.hasNext();) {
193: String key = (String) iter.next();
194: PackageProvider prov = getProvider(key, config);
195: if (prov != null) {
196: listener.info("Configuring package provider : "
197: + prov.toString());
198: provs.add(prov);
199: }
200: }
201:
202: configure(newInstance, provs, secondsToRefresh);
203: }
204:
205: /**
206: * Pass in the name and full path to a config file that is on the classpath.
207: */
208: public static RuleAgent newRuleAgent(String propsFileName) {
209: return newRuleAgent(loadFromProperties(propsFileName));
210: }
211:
212: /**
213: * Pass in the name and full path to a config file that is on the classpath.
214: */
215: public static RuleAgent newRuleAgent(String propsFileName,
216: RuleBaseConfiguration ruleBaseConfiguration) {
217: return newRuleAgent(loadFromProperties(propsFileName),
218: ruleBaseConfiguration);
219: }
220:
221: /**
222: * This takes in an optional listener. Listener must not be null in this case.
223: */
224: public static RuleAgent newRuleAgent(String propsFileName,
225: AgentEventListener listener) {
226: return newRuleAgent(loadFromProperties(propsFileName), listener);
227: }
228:
229: /**
230: * This takes in an optional listener and RuleBaseConfiguration. Listener must not be null in this case.
231: */
232: public static RuleAgent newRuleAgent(String propsFileName,
233: AgentEventListener listener,
234: RuleBaseConfiguration ruleBaseConfiguration) {
235: return newRuleAgent(loadFromProperties(propsFileName),
236: listener, ruleBaseConfiguration);
237: }
238:
239: static Properties loadFromProperties(String propsFileName) {
240: InputStream in = RuleAgent.class
241: .getResourceAsStream(propsFileName);
242: Properties props = new Properties();
243: try {
244: props.load(in);
245: return props;
246:
247: } catch (IOException e) {
248: throw new RuntimeDroolsException(
249: "Unable to load properties. Needs to be the path and name of a config file on your classpath.",
250: e);
251: }
252: }
253:
254: /**
255: * Return a configured provider ready to go.
256: */
257: private PackageProvider getProvider(String key, Properties config) {
258: if (!PACKAGE_PROVIDERS.containsKey(key)) {
259: return null;
260: }
261: Class clz = (Class) PACKAGE_PROVIDERS.get(key);
262: try {
263: PackageProvider prov = (PackageProvider) clz.newInstance();
264: prov.setAgentListener(listener);
265: prov.configure(config);
266: return prov;
267: } catch (InstantiationException e) {
268: throw new RuntimeDroolsException(
269: "Unable to load up a package provider for " + key,
270: e);
271: } catch (IllegalAccessException e) {
272: throw new RuntimeDroolsException(
273: "Unable to load up a package provider for " + key,
274: e);
275: }
276: }
277:
278: synchronized void configure(boolean newInstance, List provs,
279: int secondsToRefresh) {
280: this .newInstance = newInstance;
281: this .providers = provs;
282:
283: //run it the first time for each.
284: refreshRuleBase();
285:
286: if (secondsToRefresh != -1) {
287: startPolling(secondsToRefresh);
288: }
289:
290: }
291:
292: public void refreshRuleBase() {
293:
294: List changedPackages = new ArrayList();
295:
296: for (Iterator iter = providers.iterator(); iter.hasNext();) {
297: PackageProvider prov = (PackageProvider) iter.next();
298: Package[] changes = checkForChanges(prov);
299: if (changes != null && changes.length > 0) {
300: changedPackages.addAll(Arrays.asList(changes));
301: }
302: }
303:
304: if (changedPackages.size() > 0) {
305: listener.info("Applying changes to the rulebase.");
306: //we have a change
307: if (this .newInstance) {
308: listener
309: .info("Creating a new rulebase as per settings.");
310: //blow away old
311: this .ruleBase = RuleBaseFactory
312: .newRuleBase(this .ruleBaseConf);
313:
314: //need to store ALL packages
315: for (Iterator iter = changedPackages.iterator(); iter
316: .hasNext();) {
317: Package element = (Package) iter.next();
318: this .packages.put(element.getName(), element); //replace
319: }
320: //get packages from full name
321: PackageProvider.applyChanges(this .ruleBase, false,
322: this .packages.values(), this .listener);
323: } else {
324: PackageProvider.applyChanges(this .ruleBase, true,
325: changedPackages, this .listener);
326: }
327: }
328:
329: }
330:
331: private synchronized Package[] checkForChanges(PackageProvider prov) {
332: listener.debug("SCANNING FOR CHANGE " + prov.toString());
333: if (this .ruleBase == null)
334: ruleBase = RuleBaseFactory.newRuleBase(this .ruleBaseConf);
335: Package[] changes = prov.loadPackageChanges();
336: return changes;
337: }
338:
339: /**
340: * Convert a space seperated list into a List of stuff.
341: * @param property
342: * @return
343: */
344: static List list(String property) {
345: if (property == null)
346: return Collections.EMPTY_LIST;
347: StringTokenizer st = new StringTokenizer(property, "\n\r\t ");
348: List list = new ArrayList();
349: while (st.hasMoreTokens()) {
350: list.add(st.nextToken());
351: }
352: return list;
353: }
354:
355: /**
356: * Return a current rulebase.
357: * Depending on the configuration, this may be a new object each time
358: * the rules are updated.
359: *
360: */
361: public synchronized RuleBase getRuleBase() {
362: return this .ruleBase;
363: }
364:
365: RuleAgent(RuleBaseConfiguration ruleBaseConf) {
366: if (ruleBaseConf == null) {
367: this .ruleBaseConf = new RuleBaseConfiguration();
368: } else {
369: this .ruleBaseConf = ruleBaseConf;
370: }
371: }
372:
373: /**
374: * Stop the polling (if it is happening)
375: */
376: public synchronized void stopPolling() {
377: if (this .timer != null)
378: timer.cancel();
379: timer = null;
380: }
381:
382: /**
383: * Will start polling. If polling is already running it does nothing.
384: *
385: */
386: public synchronized void startPolling() {
387: if (this .timer == null) {
388: startPolling(this .secondsToRefresh);
389: }
390: }
391:
392: /**
393: * Will start polling. If polling is already happening and of the same interval
394: * it will do nothing, if the interval is different it will stop the current Timer
395: * and create a new Timer for the new interval.
396: * @param secondsToRefresh
397: */
398: public synchronized void startPolling(int secondsToRefresh) {
399: if (this .timer != null) {
400: if (this .secondsToRefresh != secondsToRefresh) {
401: stopPolling();
402: } else {
403: // do nothing.
404: return;
405: }
406: }
407:
408: this .secondsToRefresh = secondsToRefresh;
409: int interval = this .secondsToRefresh * 1000;
410: //now schedule it for polling
411: timer = new Timer(true);
412: timer.schedule(new TimerTask() {
413: public void run() {
414: try {
415:
416: listener.debug("Checking for updates.");
417: refreshRuleBase();
418:
419: } catch (Exception e) {
420: //don't want to stop execution here.
421: listener.exception(e);
422: }
423: }
424: }, interval, interval);
425: }
426:
427: boolean isNewInstance() {
428: return newInstance;
429: }
430:
431: public synchronized boolean isPolling() {
432: return this .timer != null;
433: }
434:
435: /**
436: * This should only be used once, on setup.
437: * @return
438: */
439: private AgentEventListener getDefaultListener() {
440:
441: return new AgentEventListener() {
442:
443: private String name;
444:
445: public String time() {
446: Date d = new Date();
447: return d.toString();
448: }
449:
450: public void exception(Exception e) {
451: System.err.println("RuleAgent(" + name
452: + ") EXCEPTION (" + time() + "): "
453: + e.getMessage()
454: + ". Stack trace should follow.");
455: e.printStackTrace(System.err);
456: }
457:
458: public void info(String message) {
459: System.err.println("RuleAgent(" + name + ") INFO ("
460: + time() + "): " + message);
461: }
462:
463: public void warning(String message) {
464: System.err.println("RuleAgent(" + name + ") WARNING ("
465: + time() + "): " + message);
466: }
467:
468: public void debug(String message) {
469: //do nothing...
470: }
471:
472: public void setAgentName(String name) {
473: this.name = name;
474:
475: }
476:
477: };
478: }
479:
480: }
|