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