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    }