Java tutorial
/* * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.mobicents.servlet.sip.restcomm.interpreter; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import org.apache.commons.configuration.Configuration; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.utils.URLEncodedUtils; import org.apache.log4j.Logger; import org.joda.time.DateTime; import org.mobicents.servlet.sip.restcomm.FiniteStateMachine; import org.mobicents.servlet.sip.restcomm.ServiceLocator; import org.mobicents.servlet.sip.restcomm.Sid; import org.mobicents.servlet.sip.restcomm.State; import org.mobicents.servlet.sip.restcomm.annotations.concurrency.NotThreadSafe; import org.mobicents.servlet.sip.restcomm.dao.DaoManager; import org.mobicents.servlet.sip.restcomm.dao.NotificationsDao; import org.mobicents.servlet.sip.restcomm.entities.Notification; import org.mobicents.servlet.sip.restcomm.interpreter.http.HttpRequestDescriptor; import org.mobicents.servlet.sip.restcomm.interpreter.http.HttpRequestExecutor; import org.mobicents.servlet.sip.restcomm.interpreter.http.HttpResponseDescriptor; import org.mobicents.servlet.sip.restcomm.util.HttpUtils; import org.mobicents.servlet.sip.restcomm.util.StringUtils; import org.mobicents.servlet.sip.restcomm.xml.RcmlDocument; import org.mobicents.servlet.sip.restcomm.xml.RcmlDocumentBuilder; import org.mobicents.servlet.sip.restcomm.xml.RcmlDocumentBuilderException; import org.mobicents.servlet.sip.restcomm.xml.Tag; import org.mobicents.servlet.sip.restcomm.xml.TagIterator; import org.mobicents.servlet.sip.restcomm.xml.TagVisitor; import org.mobicents.servlet.sip.restcomm.xml.VisitorException; import org.mobicents.servlet.sip.restcomm.xml.rcml.RcmlTag; import org.mobicents.servlet.sip.restcomm.xml.rcml.RcmlTagFactory; /** * @author quintana.thomas@gmail.com (Thomas Quintana) */ @NotThreadSafe public abstract class RcmlInterpreter extends FiniteStateMachine implements Runnable, TagVisitor { private static final Logger logger = Logger.getLogger(RcmlInterpreter.class); // RcmlInterpreter states. protected static final State IDLE = new State("idle"); protected static final State REDIRECTED = new State("redirected"); protected static final State READY = new State("ready"); protected static final State EXECUTING = new State("executing"); protected static final State FINISHED = new State("finished"); protected static final State FAILED = new State("failed"); static { IDLE.addTransition(READY); IDLE.addTransition(FINISHED); READY.addTransition(EXECUTING); READY.addTransition(FINISHED); EXECUTING.addTransition(READY); EXECUTING.addTransition(FINISHED); EXECUTING.addTransition(REDIRECTED); EXECUTING.addTransition(FAILED); REDIRECTED.addTransition(READY); } // Configuration private final Configuration configuration; // RcmlInterpreter environment. private final RcmlInterpreterContext context; //Data Access Object Manager. private final DaoManager daos; //Tag strategy strategies. private final TagStrategyFactory strategies; // XML Resource Builder. private final RcmlDocumentBuilder resourceBuilder; // XML Resource. private RcmlDocument resource; // XML Resource URI. private URI resourceUri; // XML Resource request attributes. private String requestParameters; // XML Resource request requestMethod. private String requestMethod; // XML Resource response body. private String responseBody; // XML Resource response headers. private String responseHeaders; // The thread executing this interpreter. private Thread thread; public RcmlInterpreter(final RcmlInterpreterContext context, final TagStrategyFactory strategies) { super(IDLE); addState(IDLE); addState(REDIRECTED); addState(READY); addState(EXECUTING); addState(FINISHED); addState(FAILED); this.context = context; this.strategies = strategies; this.resourceBuilder = new RcmlDocumentBuilder(new RcmlTagFactory()); final ServiceLocator services = ServiceLocator.getInstance(); configuration = services.get(Configuration.class); daos = services.get(DaoManager.class); } protected abstract void cleanup(); public void failed() { assertState(EXECUTING); setState(FAILED); } public void finish() { final State state = getState(); if (!FINISHED.equals(state) && !FAILED.equals(state)) { setState(FINISHED); } } public void finishAndInterrupt() { finish(); interruptThread(); } public URI getCurrentResourceUri() { return resourceUri; } private URI getMoreInfo(final int errorCode) { String errorDictionary = configuration.getString("error-dictionary-uri"); errorDictionary = StringUtils.addSuffixIfNotPresent(errorDictionary, "/"); final StringBuilder buffer = new StringBuilder(); buffer.append(errorDictionary).append(errorCode).append(".html"); return URI.create(buffer.toString()); } private void interruptThread() { if (thread != null) { final Thread.State state = thread.getState(); if (Thread.State.BLOCKED == state || Thread.State.TIMED_WAITING == state || Thread.State.WAITING == state) { thread.interrupt(); } } } protected abstract void initialize(); public boolean isRunning() { final State state = getState(); return IDLE.equals(state) || READY.equals(state) || EXECUTING.equals(state) || REDIRECTED.equals(state); } public synchronized void join() throws InterruptedException { if (isRunning()) { wait(); } } public void load(final URI uri, final String method, List<NameValuePair> parameters) throws InterpreterException { final List<State> possibleStates = new ArrayList<State>(); possibleStates.add(IDLE); possibleStates.add(EXECUTING); assertState(possibleStates); // Load the XML resource for execution. HttpRequestDescriptor request = null; try { request = new HttpRequestDescriptor(uri, method, parameters); } catch (final UnsupportedEncodingException ignored) { } catch (final URISyntaxException exception) { save(notify(context, Notification.ERROR, 11100)); throw new InterpreterException(exception); } resourceUri = request.getUri(); requestMethod = request.getMethod(); requestParameters = URLEncodedUtils.format(request.getParameters(), "UTF-8"); try { final HttpRequestExecutor executor = new HttpRequestExecutor(); final HttpResponseDescriptor response = executor.execute(request); responseHeaders = HttpUtils.toString(response.getHeaders()); if (HttpStatus.SC_OK == response.getStatusCode() && (response.getContentLength() > 0 || response.isChunked())) { final String contentType = response.getContentType(); if (contentType.contains("text/xml") || contentType.contains("application/xml") || contentType.contains("text/html")) { final String data = StringUtils.toString(response.getContent()); responseBody = data; resource = resourceBuilder.build(data); } else if (contentType.contains("audio/wav") || contentType.contains("audio/wave") || contentType.contains("audio/x-wav")) { resource = resourceBuilder.build(loadWav(response.getContent())); } else if (contentType.contains("text/plain")) { final String data = StringUtils.toString(response.getContent()); responseBody = data; resource = resourceBuilder.build(loadPlainText(data)); } else { save(notify(context, Notification.ERROR, 12300)); throw new InterpreterException("Invalid content type " + contentType); } if (!"Response".equals(resource.getName())) { save(notify(context, Notification.ERROR, 12102)); throw new InterpreterException( "Invalid document root for document located @ " + request.getUri().toString()); } } else { save(notify(context, Notification.ERROR, 11200)); throw new InterpreterException("Received an unsucessful response when requesting a document @ " + request.getUri().toString()); } } catch (final IOException exception) { save(notify(context, Notification.ERROR, 11200)); throw new InterpreterException(exception); } catch (final RcmlDocumentBuilderException exception) { save(notify(context, Notification.ERROR, 12100)); throw new InterpreterException(exception); } catch (final URISyntaxException exception) { save(notify(context, Notification.ERROR, 11100)); throw new InterpreterException(exception); } } public String loadPlainText(final String text) { return new StringBuilder().append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>").append("<Response><Say>") .append(text).append("</Say></Response>").toString(); } public String loadWav(final InputStream input) { return new StringBuilder().append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>").append("<Response><Play>") .append("</Play></Response>").toString(); } public Notification notify(final RcmlInterpreterContext context, final int log, final int errorCode) { return notify(context, log, errorCode, resourceUri, requestMethod, requestParameters, responseBody, responseHeaders); } public Notification notify(final RcmlInterpreterContext context, final int log, final int errorCode, final URI resourceUri, final String requestMethod, final String requestVariables, final String responseBody, final String responseHeaders) { final Notification.Builder builder = Notification.builder(); final Sid sid = Sid.generate(Sid.Type.NOTIFICATION); builder.setSid(sid); builder.setAccountSid(context.getAccountSid()); builder.setApiVersion(context.getApiVersion()); builder.setLog(log); builder.setErrorCode(errorCode); builder.setMoreInfo(getMoreInfo(errorCode)); // Fix Me: This is going to have to be resolved in Beta 2 release. Create proper message text. // This has been documented in Issue# 55 @ http://code.google.com/p/restcomm/issues/detail?id=55 builder.setMessageText(new String()); builder.setMessageDate(DateTime.now()); builder.setRequestUrl(resourceUri); builder.setRequestMethod(requestMethod); builder.setRequestVariables(requestVariables); builder.setResponseBody(responseBody); builder.setResponseHeaders(responseHeaders); String rootUri = configuration.getString("root-uri"); rootUri = StringUtils.addSuffixIfNotPresent(rootUri, "/"); final StringBuilder buffer = new StringBuilder(); buffer.append(rootUri).append(context.getApiVersion()).append("/Accounts/"); buffer.append(context.getAccountSid().toString()).append("/Notifications/"); buffer.append(sid.toString()); final URI uri = URI.create(buffer.toString()); builder.setUri(uri); return builder.build(); } public void redirect() { assertState(EXECUTING); setState(REDIRECTED); } public void redirectAndInterrupt() { redirect(); interruptThread(); } public void run() { // Make sure we keep a reference to the thread executing // this interpreter so we can interrupt it if necessary. thread = Thread.currentThread(); // Initialize the interpreter. initialize(); while (getState().equals(READY)) { // Start executing the document. TagIterator iterator = resource.iterator(); while (iterator.hasNext()) { final RcmlTag tag = (RcmlTag) iterator.next(); if (!tag.hasBeenVisited() && tag.isVerb()) { // Make sure we're ready to execute the next tag. assertState(READY); setState(EXECUTING); // Try to execute the next tag. try { tag.accept(this); } catch (final VisitorException exception) { logger.warn(exception); } tag.setHasBeenVisited(true); // Handle any state changes caused by executing the tag. final State state = getState(); if (state.equals(REDIRECTED)) { iterator = resource.iterator(); setState(READY); } else if (state.equals(FINISHED) || state.equals(FAILED)) { break; } else { setState(READY); } } } finish(); } cleanup(); synchronized (this) { notifyAll(); } thread = null; } public void save(final Notification notification) { final NotificationsDao dao = daos.getNotificationsDao(); dao.addNotification(notification); } public void sendStatusCallback() { final URI uri = context.getStatusCallback(); final String method = context.getStatusCallbackMethod(); final List<NameValuePair> parameters = context.getRcmlRequestParameters(); if (uri != null) { try { final HttpRequestExecutor executor = new HttpRequestExecutor(); final HttpRequestDescriptor request = new HttpRequestDescriptor(uri, method, parameters); executor.execute(request); } catch (final UnsupportedEncodingException exception) { } catch (final URISyntaxException exception) { save(notify(context, Notification.ERROR, 21609, uri, method, URLEncodedUtils.format(parameters, "UTF-8"), null, null)); } catch (final ClientProtocolException exception) { save(notify(context, Notification.ERROR, 11206, uri, method, URLEncodedUtils.format(parameters, "UTF-8"), null, null)); } catch (final IllegalArgumentException exception) { } catch (final IOException exception) { save(notify(context, Notification.ERROR, 11200, uri, method, URLEncodedUtils.format(parameters, "UTF-8"), null, null)); } } } public void visit(final Tag tag) throws VisitorException { if (tag instanceof RcmlTag) { final RcmlTag rcmlTag = (RcmlTag) tag; try { final TagStrategy strategy = strategies.getTagStrategyInstance(tag.getName()); strategy.initialize(this, context, rcmlTag); strategy.execute(this, context, rcmlTag); } catch (final Exception exception) { throw new VisitorException(exception); } } } }