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 }