001: /*
002: * hammurapi-rules @mesopotamia.version@
003: * Hammurapi rules engine.
004: * Copyright (C) 2005 Hammurapi Group
005: *
006: * This program is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2 of the License, or (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * URL: http://http://www.hammurapi.biz
021: * e-Mail: support@hammurapi.biz
022: */
023: package biz.hammurapi.rules;
024:
025: import java.lang.ref.Reference;
026: import java.lang.ref.WeakReference;
027: import java.lang.reflect.Constructor;
028: import java.lang.reflect.InvocationTargetException;
029: import java.util.ArrayList;
030: import java.util.Collection;
031: import java.util.Iterator;
032: import java.util.Map;
033: import java.util.WeakHashMap;
034:
035: import org.w3c.dom.Element;
036: import org.w3c.dom.Node;
037:
038: import biz.hammurapi.config.ConfigurationException;
039: import biz.hammurapi.config.Context;
040: import biz.hammurapi.dispatch.DispatchException;
041: import biz.hammurapi.dispatch.QueuingDispatcher;
042: import biz.hammurapi.util.Worker;
043:
044: /**
045: * This rules container uses QueuingDispatcher and negation semantics for remove().
046: * It can also leverage worker (thread pool) provided in <code>worker-ref</code> attribute
047: * for multithreaded inference.
048: * This container also uses loop detection.
049: * @author Pavel Vlasov
050: * @revision $Revision$
051: */
052: public class QueueingRulesContainer extends RulesContainerBase {
053:
054: private FactDispatcher dispatcher;
055: private FactDispatcher removeDispatcher;
056: private CollectionManager collectionManager;
057: private HandleManager handleManager;
058: private boolean doTracing;
059:
060: public QueueingRulesContainer() {
061: super ();
062: doTracing = this instanceof ActionTracer;
063: }
064:
065: /**
066: * Adds object to handle manager and dispatches to rules.
067: * If the object is instance of Negator the it is presented to collection manager.
068: */
069: public void add(Object obj) {
070: // Remove negators negated by this and then don't dispatch this if it is
071: // negated.
072: if (negators != null) {
073: synchronized (negators) {
074: // Remove negators negated by obj.
075: if (obj instanceof Negator) {
076: Iterator it = negators.iterator();
077: while (it.hasNext()) {
078: if (Conclusion.object2Negator(it.next(),
079: (Negator) obj)) {
080: it.remove();
081: }
082: }
083: }
084:
085: // Do not dispatch object if it is negated
086: Iterator it = negators.iterator();
087: while (it.hasNext()) {
088: if (Conclusion.object2Negator(obj, (Negator) it
089: .next())) {
090: return;
091: }
092: }
093:
094: }
095: }
096: dispatcher.dispatch(obj);
097: }
098:
099: /**
100: * Consumes DispatchException.
101: * This implementation just prints stack trace.
102: * Override if needed.
103: * @param exception
104: */
105: protected void onDispatchException(DispatchException exception) {
106: exception.printStackTrace();
107: }
108:
109: private Map recentFacts = new WeakHashMap();
110:
111: /**
112: * Checks recent conclusions and derivation depth to discard conclusions which came through dispatching too many times
113: * or conclusions with too big depth.
114: * @param object
115: * @return true if there is no loop
116: */
117: protected boolean checkLoop(Object object) {
118: if (object instanceof Conclusion) {
119: Conclusion cb = (Conclusion) object;
120:
121: synchronized (recentFacts) {
122: Reference prevRef = (Reference) recentFacts.get(object);
123: Conclusion prev = prevRef == null ? null
124: : (Conclusion) prevRef.get();
125:
126: if (prev == null) {
127: recentFacts.put(object, new WeakReference(object));
128: } else {
129: /**
130: * If conclusion recently passed dispatching then it is discarded, but
131: * derivations are merged if new conclusion is not derived from existing equal conclusion.
132: * This statement prevents logical loops like Parent -> Child -> Parent
133: */
134: if (prev != cb && !cb.isDerivedFrom(prev)) {
135: cb.mergeDerivations(prev);
136: }
137:
138: return false;
139: }
140: }
141:
142: /**
143: * If depth of the conclusion in more than 3 times number of handlers then such conclusion is
144: * discarded because most probably there is a logical loop.
145: */
146: if (cb.getDepth() > dispatcher.size() * 3) {
147: onDiscardedConclusion((Conclusion) object);
148: return false;
149: }
150: }
151:
152: return true;
153: }
154:
155: /**
156: * Conclusions discarded because their derivation depth is too big are passed to this method.
157: * The method does nothing. Override as needed.
158: * @param conclusion
159: */
160: protected void onDiscardedConclusion(Conclusion conclusion) {
161: // Nothing - override if needed.
162: }
163:
164: /**
165: * Removes object from handle manager and recent facts. Then creates a negator, negates facts in collection manager and
166: * then dispatches the object to remove methods.
167: * Subclasses implementing ActionTracing undo actions instead of using a negator.
168: */
169: public void remove(Object obj) {
170: if (doTracing) {
171: Collection objectActions = getObjectActions(obj);
172: if (objectActions != null) {
173: Iterator it = objectActions.iterator();
174: while (it.hasNext()) {
175: ((Action) it.next()).undo();
176: }
177: }
178: } else {
179: processNegator(newNegator(obj));
180: }
181: removeDispatcher.dispatch(obj);
182: }
183:
184: /**
185: * Returns actions performed when object was added to the database.
186: * This method must be overriden by subclasses which implement ActionTracer.
187: * @param obj
188: * @return
189: */
190: protected Collection getObjectActions(Object obj) {
191: throw new UnsupportedOperationException(
192: "Subclasses which implement ActionTracer must override this method");
193: }
194:
195: /**
196: * Instantiates new negator.
197: * @param obj
198: * @return
199: */
200: protected Negator newNegator(Object obj) {
201: try {
202: return (Negator) (negatorConstructor == null ? new EqualityNegator(
203: obj)
204: : negatorConstructor
205: .newInstance(new Object[] { obj }));
206: } catch (InstantiationException e) {
207: throw new RuntimeException(
208: "Could not instantiate negator: " + e, e);
209: } catch (IllegalAccessException e) {
210: throw new RuntimeException(
211: "Could not instantiate negator: " + e, e);
212: } catch (InvocationTargetException e) {
213: throw new RuntimeException(
214: "Could not instantiate negator: " + e, e);
215: }
216: }
217:
218: private void processNegator(Negator negator) {
219: // Remove from negators
220: if (negators != null) {
221: synchronized (negators) {
222: Iterator it = negators.iterator();
223: while (it.hasNext()) {
224: if (Conclusion.object2Negator(it.next(), negator)) {
225: it.remove();
226: }
227: }
228: }
229: }
230:
231: synchronized (recentFacts) {
232: Iterator it = recentFacts.keySet().iterator();
233: while (it.hasNext()) {
234: if (Conclusion.object2Negator(it.next(), negator)) {
235: it.remove();
236: }
237: }
238: }
239: handleManager.isNegatedBy(negator);
240: collectionManager.isNegatedBy(negator);
241: dispatcher.isNegatedBy(negator);
242: removeDispatcher.isNegatedBy(negator);
243: }
244:
245: /**
246: * Invokes dispatcher's join() to wait until all
247: * jobs are finished.
248: */
249: public void executeRules() {
250: try {
251: dispatcher.join();
252: removeDispatcher.join();
253: } catch (InterruptedException e) {
254: throw new RulesRuntimeException("Wait interrupted: " + e, e);
255: }
256: }
257:
258: private String workerRef;
259:
260: /**
261: * If this it true then negators posted to the bus are put to a collection and all new facts are checked
262: * against negators before being posted.
263: */
264: private Collection negators;
265:
266: private Constructor negatorConstructor;
267:
268: public void configure(Node configNode, Context context)
269: throws ConfigurationException {
270: super .configure(configNode, context);
271: Element ce = (Element) configNode;
272: if (ce.hasAttribute("worker-ref")) {
273: workerRef = ce.getAttribute("worker-ref");
274: }
275:
276: if (ce.hasAttribute("retain-negators")
277: && "yes".equals(ce.getAttribute("retain-negators"))) {
278: negators = new ArrayList();
279: }
280:
281: if (ce.hasAttribute("negator-class")) {
282: try {
283: Class negatorClass = Class.forName(ce
284: .getAttribute("negator-class"));
285: negatorConstructor = negatorClass
286: .getConstructor(new Class[] { Object.class });
287: } catch (ClassNotFoundException e) {
288: throw new ConfigurationException(
289: "Negator class not found: " + e, e);
290: } catch (SecurityException e) {
291: throw new ConfigurationException(
292: "Cannot access negator constructor: " + e, e);
293: } catch (NoSuchMethodException e) {
294: throw new ConfigurationException(
295: "Negator constructor not found: " + e, e);
296: }
297: }
298: }
299:
300: private class FactDispatcher extends QueuingDispatcher implements
301: Negatable {
302:
303: /**
304: * @param targets
305: * @param worker
306: */
307: public FactDispatcher(Collection targets, Worker worker) {
308: super (targets, worker);
309: }
310:
311: /**
312: * Removes negated object from queue
313: */
314: public boolean isNegatedBy(Negator negator) {
315: synchronized (queue) {
316: Iterator it = queue.iterator();
317: while (it.hasNext()) {
318: Object job = it.next();
319: if (job instanceof DispatchJob
320: && Conclusion.object2Negator(
321: ((DispatchJob) job).getPayload(),
322: negator)) {
323: it.remove();
324: ((DispatchJob) job).done();
325: }
326: }
327: }
328: return false;
329: }
330:
331: }
332:
333: public void start() throws ConfigurationException {
334: super .start();
335: collectionManager = (CollectionManager) get("/collection-manager");
336: handleManager = (HandleManager) get("/handle-manager");
337: Worker worker = (Worker) (workerRef == null ? null
338: : get(workerRef));
339:
340: dispatcher = new FactDispatcher(getComponents(), worker) {
341:
342: public void dispatch(final Object obj) {
343: if (obj instanceof DispatchException) {
344: onDispatchException((DispatchException) obj);
345: } else if (checkLoop(obj)) {
346: // Negate collections if negator
347: if (obj instanceof Negator) {
348: // Post negation job to queue.
349: postJobToQueue(new Runnable() {
350:
351: public void run() {
352: processNegator((Negator) obj);
353: }
354:
355: });
356: }
357:
358: // Add only public facts to the handle manager.
359: if (!(obj instanceof Fact && ((Fact) obj)
360: .isPrivate())) {
361: handleManager.addObject(obj);
362: }
363: super .dispatch(obj);
364: }
365: }
366: };
367: Collection removeHandlers = new ArrayList();
368: Iterator it = getComponents().iterator();
369: while (it.hasNext()) {
370: AbstractRule rule = (AbstractRule) it.next();
371: removeHandlers.addAll(rule.getRemoveHandlers());
372: }
373:
374: removeDispatcher = new FactDispatcher(removeHandlers, worker);
375: }
376:
377: public void stop() throws ConfigurationException {
378: try {
379: dispatcher.stop();
380: } catch (InterruptedException e) {
381: throw new ConfigurationException(
382: "Could not stop dispatcher: " + e, e);
383: }
384: try {
385: removeDispatcher.stop();
386: } catch (InterruptedException e) {
387: throw new ConfigurationException(
388: "Could not stop remove dispatcher: " + e, e);
389: }
390: super .stop();
391: }
392:
393: public void reset() {
394: Iterator it = getComponents().iterator();
395: while (it.hasNext()) {
396: ((AbstractRule) it.next()).reset();
397: }
398: }
399: }
|