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 }