001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.servicemix.drools;
018    
019    import java.io.InputStream;
020    import java.io.InputStreamReader;
021    import java.net.URL;
022    import java.util.List;
023    import java.util.Map;
024    import java.util.concurrent.ConcurrentHashMap;
025    import java.util.concurrent.ConcurrentMap;
026    
027    import javax.jbi.JBIException;
028    import javax.jbi.management.DeploymentException;
029    import javax.jbi.messaging.ExchangeStatus;
030    import javax.jbi.messaging.Fault;
031    import javax.jbi.messaging.InOnly;
032    import javax.jbi.messaging.InOptionalOut;
033    import javax.jbi.messaging.InOut;
034    import javax.jbi.messaging.MessageExchange;
035    import javax.jbi.messaging.MessagingException;
036    import javax.jbi.messaging.NormalizedMessage;
037    import javax.jbi.messaging.MessageExchange.Role;
038    import javax.jbi.servicedesc.ServiceEndpoint;
039    import javax.xml.namespace.NamespaceContext;
040    import javax.xml.namespace.QName;
041    
042    import org.apache.servicemix.common.DefaultComponent;
043    import org.apache.servicemix.common.JbiConstants;
044    import org.apache.servicemix.common.ServiceUnit;
045    import org.apache.servicemix.common.endpoints.ProviderEndpoint;
046    import org.apache.servicemix.common.util.MessageUtil;
047    import org.apache.servicemix.drools.model.Exchange;
048    import org.drools.RuleBase;
049    import org.drools.compiler.RuleBaseLoader;
050    import org.springframework.core.io.Resource;
051    
052    /**
053     * 
054     * @author gnodet
055     * @org.apache.xbean.XBean element="endpoint"
056     */
057    
058    public class DroolsEndpoint extends ProviderEndpoint {
059    
060        private RuleBase ruleBase;
061        private Resource ruleBaseResource;
062        private URL ruleBaseURL;
063        private NamespaceContext namespaceContext;
064        private QName defaultTargetService;
065        private String defaultTargetURI;
066        private Map<String, Object> globals;
067        private List<Object> assertedObjects;
068        private boolean autoReply;
069        
070        @SuppressWarnings("serial")
071        private ConcurrentMap<String, DroolsExecutionContext> pending = new ConcurrentHashMap<String, DroolsExecutionContext>() {
072            public DroolsExecutionContext remove(Object key) {
073                DroolsExecutionContext context = super.remove(key);
074                if (context != null) {
075                  // stop the execution context -- updating and disposing of any working memory
076                  context.update();
077                  context.stop();
078                }
079                return context;
080            };
081        };
082    
083        public DroolsEndpoint() {
084            super();
085        }
086    
087        public DroolsEndpoint(DefaultComponent component, ServiceEndpoint endpoint) {
088            super(component, endpoint);
089        }
090    
091        public DroolsEndpoint(ServiceUnit su, QName service, String endpoint) {
092            super(su, service, endpoint);
093        }
094    
095        /**
096         * @return the ruleBase
097         */
098        public RuleBase getRuleBase() {
099            return ruleBase;
100        }
101    
102        /**
103         * @param ruleBase the ruleBase to set
104         */
105        public void setRuleBase(RuleBase ruleBase) {
106            this.ruleBase = ruleBase;
107        }
108    
109        /**
110         * @return the ruleBaseResource
111         */
112        public Resource getRuleBaseResource() {
113            return ruleBaseResource;
114        }
115    
116        /**
117         * @param ruleBaseResource the ruleBaseResource to set
118         */
119        public void setRuleBaseResource(Resource ruleBaseResource) {
120            this.ruleBaseResource = ruleBaseResource;
121        }
122    
123        /**
124         * @return the ruleBaseURL
125         */
126        public URL getRuleBaseURL() {
127            return ruleBaseURL;
128        }
129    
130        /**
131         * @param ruleBaseURL the ruleBaseURL to set
132         */
133        public void setRuleBaseURL(URL ruleBaseURL) {
134            this.ruleBaseURL = ruleBaseURL;
135        }
136    
137        /**
138         * @return the namespaceContext
139         */
140        public NamespaceContext getNamespaceContext() {
141            return namespaceContext;
142        }
143    
144        /**
145         * @param namespaceContext the namespaceContext to set
146         */
147        public void setNamespaceContext(NamespaceContext namespaceContext) {
148            this.namespaceContext = namespaceContext;
149        }
150    
151        /**
152         * @return the variables
153         */
154        public Map<String, Object> getGlobals() {
155            return globals;
156        }
157    
158        /**
159         * @param variables the variables to set
160         */
161        public void setGlobals(Map<String, Object> variables) {
162            this.globals = variables;
163        }
164        
165        /**
166         * Will this endpoint automatically reply to any exchanges not handled by the Drools rulebase?
167         * 
168         * @return <code>true</code> if the endpoint replies to any unanswered exchanges
169         */
170        public boolean isAutoReply() {
171            return autoReply;
172        }
173        
174        /**
175         * Set auto-reply to <code>true</code> to ensure that every exchange is being replied to.
176         * This way, you can avoid having to end every Drools rule with jbi.answer()
177         * 
178         * @param autoReply <code>true</code> for auto-replying on incoming exchanges 
179         */
180        public void setAutoReply(boolean autoReply) {
181            this.autoReply = autoReply;
182        }
183    
184        public void validate() throws DeploymentException {
185            super.validate();
186            if (ruleBase == null && ruleBaseResource == null && ruleBaseURL == null) {
187                throw new DeploymentException("Property ruleBase, ruleBaseResource or ruleBaseURL must be set");
188            }
189        }
190        
191        public void start() throws Exception {
192            super.start();
193            if (ruleBase == null) {
194                InputStream is = null;
195                try {
196                    if (ruleBaseResource != null) {
197                        is = ruleBaseResource.getInputStream();
198                    } else if (ruleBaseURL != null) {
199                        is = ruleBaseURL.openStream();
200                    } else {
201                        throw new IllegalArgumentException("Property ruleBase, ruleBaseResource "
202                                + "or ruleBaseURL must be set");
203                    }
204                    RuleBaseLoader loader = RuleBaseLoader.getInstance();
205                    ruleBase = loader.loadFromReader(new InputStreamReader(is));
206                } catch (Exception e) {
207                    throw new JBIException(e);
208                } finally {
209                    if (is != null) {
210                        is.close();
211                    }
212                }
213            }
214        }
215    
216        /* (non-Javadoc)
217         * @see org.apache.servicemix.common.endpoints.ProviderEndpoint#process(
218         *      javax.jbi.messaging.MessageExchange, javax.jbi.messaging.NormalizedMessage)
219         */
220        public void process(MessageExchange exchange) throws Exception {
221            if (exchange.getRole() == Role.PROVIDER) {
222                handleProviderExchange(exchange);
223            } else {
224                handleConsumerExchange(exchange);
225            }
226        }
227        
228        /*
229         * Handle a consumer exchange
230         */
231        private void handleConsumerExchange(MessageExchange exchange) throws MessagingException {
232            String correlation = (String) exchange.getProperty(DroolsComponent.DROOLS_CORRELATION_ID); 
233            DroolsExecutionContext drools = pending.get(correlation);
234            if (drools != null) {
235                MessageExchange original = drools.getExchange();
236                if (exchange.getStatus() == ExchangeStatus.DONE) {
237                    done(original);
238                } else if (exchange.getStatus() == ExchangeStatus.ERROR) {
239                    fail(original, exchange.getError());
240                } else {
241                    if (exchange.getFault() != null) {
242                        MessageUtil.transferFaultToFault(exchange, original);
243                    } else {
244                        MessageUtil.transferOutToOut(exchange, original);
245                    }
246                    // TODO: remove this sendSync() and replace by a send()
247                    // TODO: there is a need to store the exchange and send the DONE
248                    // TODO: when the original comes back
249                    sendSync(original);
250                    done(exchange);
251                }
252            } else {
253                logger.debug("No pending exchange found for " + correlation + ", no additional rules will be triggered");
254            }
255        }
256    
257        private void handleProviderExchange(MessageExchange exchange) throws Exception {
258            if (exchange.getStatus() == ExchangeStatus.ACTIVE) {
259                drools(exchange);
260            } else {
261                //must be a DONE/ERROR so removing any pending contexts
262                pending.remove(exchange.getExchangeId());
263            }
264        }
265    
266        public static String getCorrelationId(MessageExchange exchange) {
267            Object correlation = exchange.getProperty(JbiConstants.CORRELATION_ID);
268            if (correlation == null) {
269                return exchange.getExchangeId();
270            } else {
271                return correlation.toString();
272            }
273        }
274    
275        protected void drools(MessageExchange exchange) throws Exception {
276            DroolsExecutionContext drools = startDroolsExecutionContext(exchange);
277            if (drools.getRulesFired() < 1) {
278                if (getDefaultTargetService() == null) {
279                    fail(exchange, new Exception("No rules have handled the exchange. Check your rule base."));
280                } else {
281                    drools.getHelper().route(getDefaultRouteURI());
282                }
283            } else {
284                //the exchange has been answered or faulted by the drools endpoint
285                if (drools.isExchangeHandled() && exchange instanceof InOnly) {
286                    //only removing InOnly
287                    pending.remove(exchange.getExchangeId());
288                }
289                if (!drools.isExchangeHandled() && autoReply) {
290                    reply(exchange, drools);
291                }
292            }
293        }
294        
295        private void reply(MessageExchange exchange, DroolsExecutionContext drools) throws Exception {
296            Fault fault = exchange.getFault();
297            if (fault != null) {
298                drools.getHelper().fault(fault.getContent());
299            } else if (isOutCapable(exchange)) {
300                NormalizedMessage message = exchange.getMessage(Exchange.OUT_MESSAGE);
301                if (message == null) {
302                    // send back the 'in' message if no 'out' message is available
303                    message = exchange.getMessage(Exchange.IN_MESSAGE); 
304                }
305                drools.getHelper().answer(message.getContent());
306            } else if (exchange instanceof InOnly) {
307                // just send back the done
308                done(exchange);
309            }
310        }
311    
312        private boolean isOutCapable(MessageExchange exchange) {
313            return exchange instanceof InOptionalOut || exchange instanceof InOut;
314        }
315    
316        private DroolsExecutionContext startDroolsExecutionContext(MessageExchange exchange) {
317            DroolsExecutionContext drools = new DroolsExecutionContext(this, exchange);
318            pending.put(exchange.getExchangeId(), drools);
319            drools.start();
320            return drools;
321        }
322    
323    
324        public QName getDefaultTargetService() {
325            return defaultTargetService;
326        }
327    
328        public void setDefaultTargetService(QName defaultTargetService) {
329            this.defaultTargetService = defaultTargetService;
330        }
331    
332        public String getDefaultTargetURI() {
333            return defaultTargetURI;
334        }
335    
336        public void setDefaultTargetURI(String defaultTargetURI) {
337            this.defaultTargetURI = defaultTargetURI;
338        }
339    
340        public List<Object> getAssertedObjects() {
341            return assertedObjects;
342        }
343    
344        public void setAssertedObjects(List<Object> assertedObjects) {
345            this.assertedObjects = assertedObjects;
346        }
347    
348        public String getDefaultRouteURI() {
349            if (defaultTargetURI != null) {
350                return defaultTargetURI;
351            } else if (defaultTargetService != null) {
352                String nsURI = defaultTargetService.getNamespaceURI();
353                String sep = (nsURI.indexOf("/") > 0) ? "/" : ":";
354                return "service:" + nsURI + sep + defaultTargetService.getLocalPart();
355            } else {
356                return null;
357            }
358        }
359        
360        @Override
361        protected void send(MessageExchange me) throws MessagingException {
362            if (me.getStatus() != ExchangeStatus.ACTIVE) {
363                // must be a DONE/ERROR so removing any pending contexts
364                pending.remove(me.getExchangeId());
365            }
366            super.send(me);
367        }
368     }