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    }