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 /**
046 * This rules container uses QueuingDispatcher and negation semantics for remove().
047 * It can also leverage worker (thread pool) provided in <code>worker-ref</code> attribute
048 * for multithreaded inference.
049 * This container also uses loop detection.
050 * @author Pavel Vlasov
051 * @revision $Revision$
052 */
053 public class QueueingRulesContainer extends RulesContainerBase {
054
055 private FactDispatcher dispatcher;
056 private FactDispatcher removeDispatcher;
057 private CollectionManager collectionManager;
058 private HandleManager handleManager;
059 private boolean doTracing;
060
061 public QueueingRulesContainer() {
062 super();
063 doTracing = this instanceof ActionTracer;
064 }
065
066 /**
067 * Adds object to handle manager and dispatches to rules.
068 * If the object is instance of Negator the it is presented to collection manager.
069 */
070 public void add(Object obj) {
071 // Remove negators negated by this and then don't dispatch this if it is
072 // negated.
073 if (negators!=null) {
074 synchronized (negators) {
075 // Remove negators negated by obj.
076 if (obj instanceof Negator) {
077 Iterator it=negators.iterator();
078 while (it.hasNext()) {
079 if (Conclusion.object2Negator(it.next(), (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.next())) {
089 return;
090 }
091 }
092
093 }
094 }
095 dispatcher.dispatch(obj);
096 }
097
098 /**
099 * Consumes DispatchException.
100 * This implementation just prints stack trace.
101 * Override if needed.
102 * @param exception
103 */
104 protected void onDispatchException(DispatchException exception) {
105 exception.printStackTrace();
106 }
107
108 private Map recentFacts=new WeakHashMap();
109
110 /**
111 * Checks recent conclusions and derivation depth to discard conclusions which came through dispatching too many times
112 * or conclusions with too big depth.
113 * @param object
114 * @return true if there is no loop
115 */
116 protected boolean checkLoop(Object object) {
117 if (object instanceof Conclusion) {
118 Conclusion cb=(Conclusion) object;
119
120 synchronized (recentFacts) {
121 Reference prevRef=(Reference) recentFacts.get(object);
122 Conclusion prev = prevRef==null ? null : (Conclusion) prevRef.get();
123
124 if (prev==null) {
125 recentFacts.put(object, new WeakReference(object));
126 } else {
127 /**
128 * If conclusion recently passed dispatching then it is discarded, but
129 * derivations are merged if new conclusion is not derived from existing equal conclusion.
130 * This statement prevents logical loops like Parent -> Child -> Parent
131 */
132 if (prev!=cb && !cb.isDerivedFrom(prev)) {
133 cb.mergeDerivations(prev);
134 }
135
136 return false;
137 }
138 }
139
140 /**
141 * If depth of the conclusion in more than 3 times number of handlers then such conclusion is
142 * discarded because most probably there is a logical loop.
143 */
144 if (cb.getDepth()>dispatcher.size()*3) {
145 onDiscardedConclusion((Conclusion) object);
146 return false;
147 }
148 }
149
150 return true;
151 }
152
153 /**
154 * Conclusions discarded because their derivation depth is too big are passed to this method.
155 * The method does nothing. Override as needed.
156 * @param conclusion
157 */
158 protected void onDiscardedConclusion(Conclusion conclusion) {
159 // Nothing - override if needed.
160 }
161
162 /**
163 * Removes object from handle manager and recent facts. Then creates a negator, negates facts in collection manager and
164 * then dispatches the object to remove methods.
165 * Subclasses implementing ActionTracing undo actions instead of using a negator.
166 */
167 public void remove(Object obj) {
168 if (doTracing) {
169 Collection objectActions = getObjectActions(obj);
170 if (objectActions!=null) {
171 Iterator it=objectActions.iterator();
172 while (it.hasNext()) {
173 ((Action) it.next()).undo();
174 }
175 }
176 } else {
177 processNegator(newNegator(obj));
178 }
179 removeDispatcher.dispatch(obj);
180 }
181
182 /**
183 * Returns actions performed when object was added to the database.
184 * This method must be overriden by subclasses which implement ActionTracer.
185 * @param obj
186 * @return
187 */
188 protected Collection getObjectActions(Object obj) {
189 throw new UnsupportedOperationException("Subclasses which implement ActionTracer must override this method");
190 }
191
192 /**
193 * Instantiates new negator.
194 * @param obj
195 * @return
196 */
197 protected Negator newNegator(Object obj) {
198 try {
199 return (Negator) (negatorConstructor == null ? new EqualityNegator(obj) : negatorConstructor.newInstance(new Object[] {obj}));
200 } catch (InstantiationException e) {
201 throw new RuntimeException("Could not instantiate negator: "+e, e);
202 } catch (IllegalAccessException e) {
203 throw new RuntimeException("Could not instantiate negator: "+e, e);
204 } catch (InvocationTargetException e) {
205 throw new RuntimeException("Could not instantiate negator: "+e, e);
206 }
207 }
208
209 private void processNegator(Negator negator) {
210 // Remove from negators
211 if (negators!=null) {
212 synchronized (negators) {
213 Iterator it=negators.iterator();
214 while (it.hasNext()) {
215 if (Conclusion.object2Negator(it.next(), negator)) {
216 it.remove();
217 }
218 }
219 }
220 }
221
222 synchronized (recentFacts) {
223 Iterator it=recentFacts.keySet().iterator();
224 while (it.hasNext()) {
225 if (Conclusion.object2Negator(it.next(), negator)) {
226 it.remove();
227 }
228 }
229 }
230 handleManager.isNegatedBy(negator);
231 collectionManager.isNegatedBy(negator);
232 dispatcher.isNegatedBy(negator);
233 removeDispatcher.isNegatedBy(negator);
234 }
235
236 /**
237 * Invokes dispatcher's join() to wait until all
238 * jobs are finished.
239 */
240 public void executeRules() {
241 try {
242 dispatcher.join();
243 removeDispatcher.join();
244 } catch (InterruptedException e) {
245 throw new RulesRuntimeException("Wait interrupted: "+e, e);
246 }
247 }
248
249 private String workerRef;
250
251 /**
252 * If this it true then negators posted to the bus are put to a collection and all new facts are checked
253 * against negators before being posted.
254 */
255 private Collection negators;
256
257 private Constructor negatorConstructor;
258
259 public void configure(Node configNode, Context context) throws ConfigurationException {
260 super.configure(configNode, context);
261 Element ce=(Element) configNode;
262 if (ce.hasAttribute("worker-ref")) {
263 workerRef=ce.getAttribute("worker-ref");
264 }
265
266 if (ce.hasAttribute("retain-negators") && "yes".equals(ce.getAttribute("retain-negators"))) {
267 negators=new ArrayList();
268 }
269
270 if (ce.hasAttribute("negator-class")) {
271 try {
272 Class negatorClass = Class.forName(ce.getAttribute("negator-class"));
273 negatorConstructor = negatorClass.getConstructor(new Class[] {Object.class});
274 } catch (ClassNotFoundException e) {
275 throw new ConfigurationException("Negator class not found: "+e, e);
276 } catch (SecurityException e) {
277 throw new ConfigurationException("Cannot access negator constructor: "+e, e);
278 } catch (NoSuchMethodException e) {
279 throw new ConfigurationException("Negator constructor not found: "+e, e);
280 }
281 }
282 }
283
284 private class FactDispatcher extends QueuingDispatcher implements Negatable {
285
286 /**
287 * @param targets
288 * @param worker
289 */
290 public FactDispatcher(Collection targets, Worker worker) {
291 super(targets, worker);
292 }
293
294
295 /**
296 * Removes negated object from queue
297 */
298 public boolean isNegatedBy(Negator negator) {
299 synchronized (queue) {
300 Iterator it=queue.iterator();
301 while (it.hasNext()) {
302 Object job = it.next();
303 if (job instanceof DispatchJob && Conclusion.object2Negator(((DispatchJob) job).getPayload(), negator)) {
304 it.remove();
305 ((DispatchJob) job).done();
306 }
307 }
308 }
309 return false;
310 }
311
312 }
313
314 public void start() throws ConfigurationException {
315 super.start();
316 collectionManager=(CollectionManager) get("/collection-manager");
317 handleManager=(HandleManager) get("/handle-manager");
318 Worker worker = (Worker) (workerRef==null ? null : get(workerRef));
319
320 dispatcher=new FactDispatcher(getComponents(), worker) {
321
322 public void dispatch(final Object obj) {
323 if (obj instanceof DispatchException) {
324 onDispatchException((DispatchException) obj);
325 } else if (checkLoop(obj)) {
326 // Negate collections if negator
327 if (obj instanceof Negator) {
328 // Post negation job to queue.
329 postJobToQueue(
330 new Runnable() {
331
332 public void run() {
333 processNegator((Negator) obj);
334 }
335
336 });
337 }
338
339 // Add only public facts to the handle manager.
340 if (!(obj instanceof Fact && ((Fact) obj).isPrivate())) {
341 handleManager.addObject(obj);
342 }
343 super.dispatch(obj);
344 }
345 }
346 };
347 Collection removeHandlers=new ArrayList();
348 Iterator it=getComponents().iterator();
349 while (it.hasNext()) {
350 AbstractRule rule=(AbstractRule) it.next();
351 removeHandlers.addAll(rule.getRemoveHandlers());
352 }
353
354 removeDispatcher=new FactDispatcher(removeHandlers, worker);
355 }
356
357 public void stop() throws ConfigurationException {
358 try {
359 dispatcher.stop();
360 } catch (InterruptedException e) {
361 throw new ConfigurationException("Could not stop dispatcher: "+e,e);
362 }
363 try {
364 removeDispatcher.stop();
365 } catch (InterruptedException e) {
366 throw new ConfigurationException("Could not stop remove dispatcher: "+e,e);
367 }
368 super.stop();
369 }
370
371 public void reset() {
372 Iterator it=getComponents().iterator();
373 while (it.hasNext()) {
374 ((AbstractRule) it.next()).reset();
375 }
376 }
377 }