package org.drools.mas.helpers; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.namespace.QName; import javax.xml.ws.BindingProvider; import org.drools.mas.ACLMessage; import org.drools.mas.Act; import org.drools.mas.Encodings; import org.drools.mas.body.acts.AbstractMessageBody; import org.drools.mas.body.acts.Inform; import org.drools.mas.body.acts.InformIf; import org.drools.mas.body.content.Action; import org.drools.mas.body.content.Query; import org.drools.mas.util.ACLMessageFactory; import org.drools.mas.util.MessageContentEncoder; import org.drools.mas.util.MessageContentFactory; import org.drools.runtime.rule.Variable; public class DialogueHelper { public static int WSDL_RETRIEVAL_TIMEOUT = 2000; public static int EXECUTOR_SERVICE_THREAD_NUMBER = 5; private int connectionTimeout = 0; private int receiveTimeout = 0; boolean multiReturnValue = false; private Encodings encode = Encodings.XML; private URL endpointURL; private QName qname; private ExecutorService executorService = Executors.newFixedThreadPool(EXECUTOR_SERVICE_THREAD_NUMBER); protected static interface DialogueHelperCommand{ void execute(); } private DialogueHelperCallback defaultDialogueHelperCallback = new DialogueHelperCallbackImpl(){ @Override public void onSuccess(List<ACLMessage> messages) { } @Override public void onError(Throwable t) { Logger.getLogger(DialogueHelper.class.getName()).log(Level.SEVERE, "Agent invocation failed", t); } @Override public long getTimeoutForResponses() { return 0; } @Override public long getMinimumWaitTimeForResponses() { return 0; } }; public DialogueHelper(String url) { this(url, 0); } public DialogueHelper(String url, Encodings enc) { this(url, enc, 0); } public DialogueHelper(String url, int wSDLRetrievalTimeout) { this(url, null, wSDLRetrievalTimeout); } public DialogueHelper(String url, Encodings enc, int wSDLRetrievalTimeout) { try { this.endpointURL = new URL(AsyncAgentService.class.getResource("."), url); } catch (MalformedURLException ex) { Logger.getLogger(DialogueHelper.class.getName()).log(Level.SEVERE, null, ex); } this.qname = new QName("http://mas.drools.org/", "AsyncAgentService"); if (enc != null){ this.encode = enc; } checkEndpointAvailability(wSDLRetrievalTimeout); } private void checkEndpointAvailability(int wSDLRetrievalTimeout) { if (wSDLRetrievalTimeout <= 0){ if (WSDL_RETRIEVAL_TIMEOUT <= 0){ return; } wSDLRetrievalTimeout = WSDL_RETRIEVAL_TIMEOUT; } try { URLConnection openConnection; openConnection = this.endpointURL.openConnection(); openConnection.setConnectTimeout(wSDLRetrievalTimeout); openConnection.connect(); } catch (IOException ex) { throw new RuntimeException(ex); } } public String invokeRequest(String sender, String receiver, String methodName, LinkedHashMap<String, Object> args, DialogueHelperCallback callback) throws UnsupportedOperationException { return this.doRequest(sender, receiver, methodName, args, callback); } public String invokeRequest(String methodName, LinkedHashMap<String, Object> args, DialogueHelperCallback callback) throws UnsupportedOperationException { return invokeRequest(UUID.randomUUID().toString(), "", methodName, args, callback); } /** * * @param sender * @param methodName * @param args * @return * @throws UnsupportedOperationException * @deprecated Without using a DialogueHelperCallback there is no way to * be notified about exceptions in the service invocation. */ @Deprecated public String invokeRequest(String sender, String methodName, LinkedHashMap<String, Object> args) throws UnsupportedOperationException { return invokeRequest(sender, "", methodName, args); } /** * * @param methodName * @param args * @return * @throws UnsupportedOperationException * @deprecated Without using a DialogueHelperCallback there is no way to * be notified about exceptions in the service invocation. */ @Deprecated public String invokeRequest(String methodName, LinkedHashMap<String, Object> args) throws UnsupportedOperationException { return invokeRequest(UUID.randomUUID().toString(), "", methodName, args); } /** * * @param sender * @param receiver * @param methodName * @param args * @return * @throws UnsupportedOperationException * @deprecated Without using a DialogueHelperCallback there is no way to * be notified about exceptions in the service invocation. */ @Deprecated public String invokeRequest(String sender, String receiver, String methodName, LinkedHashMap<String, Object> args) throws UnsupportedOperationException { return this.doRequest(sender, receiver, methodName, args, null); } protected String doRequest(String sender, String receiver, String methodName, LinkedHashMap<String, Object> args, DialogueHelperCallback callback) throws UnsupportedOperationException { multiReturnValue = false; for (Object o : args.values()) { if (o == Variable.v) { multiReturnValue = true; break; } } ACLMessageFactory factory = new ACLMessageFactory(encode); Action action = MessageContentFactory.newActionContent(methodName, args); ACLMessage req = factory.newRequestMessage(sender, receiver, action); this.tell(req, callback, callback != null); return req.getId(); } public String invokeQueryIf(String sender, String receiver, Object proposition, DialogueHelperCallback callback) { return this.doQueryIf(sender, receiver, proposition, callback); } /** * * @param sender * @param receiver * @param proposition * @return * @deprecated Without using a DialogueHelperCallback there is no way to * be notified about exceptions in the service invocation. */ @Deprecated public String invokeQueryIf(String sender, String receiver, Object proposition) { return this.doQueryIf(sender, receiver, proposition, null); } protected String doQueryIf(String sender, String receiver, Object proposition, DialogueHelperCallback callback) { ACLMessageFactory factory = new ACLMessageFactory(Encodings.XML); ACLMessage qryif = factory.newQueryIfMessage(sender, receiver, proposition); this.tell(qryif, callback, callback != null); return qryif.getId(); } public String invokeQueryRef(String sender, String receiver, Query query, DialogueHelperCallback callback) { return this.doQueryRef(sender, receiver, query, callback); } protected String doQueryRef(String sender, String receiver, Query query, DialogueHelperCallback callback) { ACLMessageFactory factory = new ACLMessageFactory(Encodings.XML); ACLMessage qryRef = factory.newQueryRefMessage(sender, receiver, query); this.tell(qryRef, callback, callback != null); return qryRef.getId(); } public String invokeInform(String sender, String receiver, Object proposition, DialogueHelperCallback callback) { return this.doInform(sender, receiver, proposition, callback); } /** * * @param sender * @param receiver * @param proposition * @return * @deprecated Without using a DialogueHelperCallback there is no way to * be notified about exceptions in the service invocation. */ @Deprecated public String invokeInform(String sender, String receiver, Object proposition) { return this.doInform(sender, receiver, proposition, null); } protected String doInform(String sender, String receiver, Object proposition, DialogueHelperCallback callback) { ACLMessageFactory factory = new ACLMessageFactory(encode); ACLMessage newInformMessage = factory.newInformMessage(sender, receiver, proposition); this.tell(newInformMessage, callback, callback != null); return newInformMessage.getId(); } public String invokeConfirm(String sender, String receiver, Object proposition, DialogueHelperCallback callback) { return this.doConfirm(sender, receiver, proposition, callback); } /** * * @param sender * @param receiver * @param proposition * @return * @deprecated Without using a DialogueHelperCallback there is no way to * be notified about exceptions in the service invocation. */ @Deprecated public String invokeConfirm(String sender, String receiver, Object proposition) { return this.doConfirm(sender, receiver, proposition, null); } protected String doConfirm(String sender, String receiver, Object proposition, DialogueHelperCallback callback) { ACLMessageFactory factory = new ACLMessageFactory(encode); ACLMessage newConfirmMessage = factory.newConfirmMessage(sender, receiver, proposition); this.tell(newConfirmMessage, callback, callback != null); return newConfirmMessage.getId(); } public String invokeDisconfirm(String sender, String receiver, Object proposition, DialogueHelperCallback callback) { return this.doDisconfirm(sender, receiver, proposition, callback); } /** * * @param sender * @param receiver * @param proposition * @return * @deprecated Without using a DialogueHelperCallback there is no way to * be notified about exceptions in the service invocation. */ @Deprecated public String invokeDisconfirm(String sender, String receiver, Object proposition) { return this.doDisconfirm(sender, receiver, proposition, null); } protected String doDisconfirm(String sender, String receiver, Object proposition, DialogueHelperCallback callback) { ACLMessageFactory factory = new ACLMessageFactory(encode); ACLMessage newDisconfirmMessage = factory.newDisconfirmMessage(sender, receiver, proposition); this.tell(newDisconfirmMessage, callback, callback != null); return newDisconfirmMessage.getId(); } public boolean validateRequestResponses(List<ACLMessage> answers) { if (answers.size() != 2) { return false; } if (Act.AGREE.equals(answers.get(1).getPerformative())) { return false; } ACLMessage answer1 = answers.get(0); if (!Act.AGREE.equals(answer1.getPerformative())) { return false; } ACLMessage answer2 = answers.get(1); Act act2 = answer2.getPerformative(); if (!(Act.INFORM.equals(act2) || Act.INFORM_REF.equals(act2))) { return false; } return true; } public Object extractReturn(ACLMessage msg, boolean decode) throws UnsupportedOperationException { if (msg == null) { return null; } AbstractMessageBody returnBody = msg.getBody(); if (returnBody == null) { return null; } if (decode) { MessageContentEncoder.decodeBody(returnBody, encode); if (returnBody instanceof Inform) { return ((Inform) returnBody).getProposition().getData(); } if (returnBody instanceof InformIf) { return ((InformIf) returnBody).getProposition().getData(); } } else { if (returnBody instanceof Inform) { return ((Inform) returnBody).getProposition().getEncodedContent(); } if (returnBody instanceof InformIf) { return ((InformIf) returnBody).getProposition().getData(); } } return returnBody; } public List<ACLMessage> getAgentAnswers(String reqId) { AsyncDroolsAgentService asyncServicePort = this.getAsyncDroolsAgentService(); return asyncServicePort.getResponses(reqId); } private AsyncDroolsAgentService getAsyncDroolsAgentService(){ if (this.endpointURL == null || this.qname == null) { throw new IllegalStateException("A Web Service URL and a QName Must be Provided for the client to work!"); } AsyncDroolsAgentService asyncServicePort = new AsyncAgentService(this.endpointURL, this.qname).getAsyncAgentServicePort(); if (connectionTimeout > 0){ ((BindingProvider)asyncServicePort).getRequestContext().put("javax.xml.ws.client.connectionTimeout", String.valueOf(connectionTimeout)); } if (receiveTimeout > 0){ ((BindingProvider)asyncServicePort).getRequestContext().put("javax.xml.ws.client.receiveTimeout", String.valueOf(receiveTimeout)); } return asyncServicePort; } protected void tell(final ACLMessage message, DialogueHelperCallback callback, final boolean waitForAnswers){ final DialogueHelperCallback finalCallback = callback != null? callback : this.defaultDialogueHelperCallback; Logger.getLogger(DialogueHelper.class.getName()).log(Level.INFO, "Preparing tell command for {0} ", message.getId()); Logger.getLogger(DialogueHelper.class.getName()).log(Level.CONFIG, "Message:\n{0} ", message); Runnable runnable = new Runnable() { AsyncDroolsAgentService asyncDroolsAgentService = getAsyncDroolsAgentService(); public void run() { try{ Logger.getLogger(DialogueHelper.class.getName()).log(Level.INFO, "Telling the agent about {0} - START", message.getId()); asyncDroolsAgentService.tell(message); Logger.getLogger(DialogueHelper.class.getName()).log(Level.INFO, "Telling the agent about {0} - DONE", message.getId()); if (waitForAnswers){ List<ACLMessage> results = this.waitForAnswers(message.getId(), finalCallback.getExpectedResponsesNumber(), finalCallback.getMinimumWaitTimeForResponses(), finalCallback.getTimeoutForResponses()); finalCallback.onSuccess(results); } }catch (Throwable t){ finalCallback.onError(t); } } private List<ACLMessage> waitForAnswers( String id, int expectedMessagesNumber, long minimumWaitTime, long timeout) throws TimeoutException { //could be the case that the client is not waiting for any answer. //In this case there's no need to invoke the agent to get any response. if (expectedMessagesNumber == 0){ return new ArrayList<ACLMessage>(); } List<ACLMessage> answers = new ArrayList<ACLMessage>(); //avoid infinite waiting loop long waitTime = minimumWaitTime <= 0 ? 1 : minimumWaitTime; do { try { Logger.getLogger(DialogueHelper.class.getName()).log(Level.INFO, "Answer for {0} is not ready, wait... ", id); Thread.sleep( waitTime ); } catch ( InterruptedException ex ) { Logger.getLogger(DialogueHelper.class.getName()).log(Level.WARNING, "Thread could not be put to sleep", ex); } List<ACLMessage> incomingAnswers = asyncDroolsAgentService.getResponses(id); answers.addAll( incomingAnswers ); Logger.getLogger(DialogueHelper.class.getName()).log(Level.INFO, "Answers for {0}: {1} (waitTime= {2}, timeout= {3}, # responsed expected= {4})",new Object[]{ id, answers.size(), waitTime, timeout, expectedMessagesNumber}); waitTime *= 2; } while ( answers.size() != expectedMessagesNumber && waitTime < timeout ); if (answers.size() < expectedMessagesNumber){ throw new TimeoutException("Expecting "+expectedMessagesNumber+" messages for message "+id+" but only received "+answers.size()+" in "+timeout+"ms"); } return answers; } }; this.executorService.submit(runnable); } public void setConnectionTimeout(int connectionTimeout) { this.connectionTimeout = connectionTimeout; } public void setReceiveTimeout(int receiveTimeout) { this.receiveTimeout = receiveTimeout; } }