org.openxdm.xcap.client.test.subscription.SubscribeDocumentTest.java Source code

Java tutorial

Introduction

Here is the source code for org.openxdm.xcap.client.test.subscription.SubscribeDocumentTest.java

Source

/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * 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.openxdm.xcap.client.test.subscription;

import static org.junit.Assert.assertTrue;
import gov.nist.javax.sip.Utils;

import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Properties;
import java.util.concurrent.Semaphore;

import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MalformedObjectNameException;
import javax.management.ReflectionException;
import javax.naming.NamingException;
import javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.DialogTerminatedEvent;
import javax.sip.IOExceptionEvent;
import javax.sip.InvalidArgumentException;
import javax.sip.ListeningPoint;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;
import javax.sip.SipException;
import javax.sip.SipFactory;
import javax.sip.SipListener;
import javax.sip.SipProvider;
import javax.sip.SipStack;
import javax.sip.TimeoutEvent;
import javax.sip.TransactionTerminatedEvent;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.address.SipURI;
import javax.sip.header.CSeqHeader;
import javax.sip.header.CallIdHeader;
import javax.sip.header.ContactHeader;
import javax.sip.header.EventHeader;
import javax.sip.header.FromHeader;
import javax.sip.header.HeaderFactory;
import javax.sip.header.MaxForwardsHeader;
import javax.sip.header.SubscriptionStateHeader;
import javax.sip.header.ToHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import junit.framework.JUnit4TestAdapter;

import org.apache.commons.httpclient.HttpException;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mobicents.xcap.client.XcapResponse;
import org.mobicents.xcap.client.uri.DocumentSelectorBuilder;
import org.mobicents.xcap.client.uri.UriBuilder;
import org.openxdm.xcap.client.test.AbstractXDMJunitTest;
import org.openxdm.xcap.client.test.ServerConfiguration;
import org.openxdm.xcap.common.xcapdiff.DocumentType;
import org.openxdm.xcap.common.xcapdiff.XcapDiff;

/**
 * first puts a new doc through xcap then subscribes it through sip, etag from xcap put response and notify must match.
 * Then update document through xcap and a notify with old and new etag should arrive.
 * Finally delete document and a notify with old etag should arrive.
 * Unsubscribe to clean up.
 */
public class SubscribeDocumentTest extends AbstractXDMJunitTest implements SipListener {

    private static Logger logger = Logger.getLogger(SubscribeDocumentTest.class);

    public static junit.framework.Test suite() {
        return new JUnit4TestAdapter(SubscribeDocumentTest.class);
    }

    protected enum Tests {
        test1, test2, test3, test4
    }

    protected Tests testRunning;

    protected String subscriberUsername = "eduardo";
    protected String domain = "openxdm.org";
    protected String subscriberSipUri = "sip:" + subscriberUsername + "@" + domain;

    protected SipProvider sipProvider;
    protected AddressFactory addressFactory;
    protected MessageFactory messageFactory;
    protected HeaderFactory headerFactory;
    protected SipStack sipStack;
    protected ContactHeader contactHeader;
    protected String notifierPort;
    protected String transport;
    protected ListeningPoint listeningPoint;

    protected Dialog subscriberDialog;
    protected String subscriberToTag;

    protected String newEtag;
    protected String previousEtag;

    // a sempahore to control processing using async events
    protected Semaphore semaphore = new Semaphore(1);

    protected String getDocumentSelector() {
        return DocumentSelectorBuilder.getUserDocumentSelectorBuilder(appUsage.getAUID(), user, documentName)
                .toPercentEncodedString();
    }

    protected URI getDocumentXcapURI() throws URISyntaxException {
        // create uri to put doc      
        UriBuilder uriBuilder = new UriBuilder()
                .setSchemeAndAuthority(
                        "http://" + ServerConfiguration.SERVER_HOST + ":" + ServerConfiguration.SERVER_PORT)
                .setXcapRoot(ServerConfiguration.SERVER_XCAP_ROOT + "/").setDocumentSelector(getDocumentSelector());
        return uriBuilder.toURI();
    }

    protected String getTest1XcapContent() {
        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
                + "<resource-lists xmlns=\"urn:ietf:params:xml:ns:resource-lists\">" + "<list>" + "<entry uri=\""
                + appUsage.getAUID() + "/users/" + user + "/" + documentName + "\"/>" + "</list>"
                + "</resource-lists>";
    }

    protected void sendTest1XcapRequest() throws IOException, URISyntaxException {
        // ensure doc does not exists
        client.delete(getDocumentXcapURI(), getAssertedUserIdHeaders(client.getHeaderFactory(), user), null);
        // send put request and get response
        XcapResponse putResponse = client.put(getDocumentXcapURI(), appUsage.getMimetype(), getTest1XcapContent(),
                getAssertedUserIdHeaders(client.getHeaderFactory(), user), null);
        // check put response
        assertTrue("Put response must exists", putResponse != null);
        assertTrue("Put response code should be 200 or 201",
                putResponse.getCode() == 201 || putResponse.getCode() == 200);
        // set initial etag
        newEtag = putResponse.getETag();
    }

    protected void sendTest2XcapRequest() throws IOException, URISyntaxException {
        String content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
                + "<resource-lists xmlns=\"urn:ietf:params:xml:ns:resource-lists\">" + "<list/>"
                + "</resource-lists>";

        // send put request and get response
        XcapResponse putResponse = client.put(getDocumentXcapURI(), appUsage.getMimetype(), content,
                getAssertedUserIdHeaders(client.getHeaderFactory(), user), null);

        // check put response
        assertTrue("Put response must exists", putResponse != null);
        assertTrue("Put response code should be 200", putResponse.getCode() == 200);

        // set etags
        previousEtag = newEtag;
        newEtag = putResponse.getETag();
    }

    protected void sendTest3XcapRequest() throws IOException, URISyntaxException {

        // send put request and get response
        XcapResponse deleteResponse = client.delete(getDocumentXcapURI(),
                getAssertedUserIdHeaders(client.getHeaderFactory(), user), null);

        // set previous etag
        previousEtag = newEtag;

        // check put response
        assertTrue("Delete response must exists", deleteResponse != null);
        assertTrue("Delete response code should be 200", deleteResponse.getCode() == 200);
    }

    /**
     * puts a new doc through xcap, store etag of response and then subscribes doc through sip
     * @throws InvalidArgumentException 
     * @throws ParseException 
     * @throws SipException 
     * @throws URISyntaxException 
     */
    @Test
    public void test() throws HttpException, IOException, JAXBException, InterruptedException, ParseException,
            InvalidArgumentException, SipException, URISyntaxException {

        // set test state machine
        testRunning = Tests.test1;

        sendTest1XcapRequest();

        // send subscribe
        sendInitialSubscribe();

        // let's wait for test to succeed or timeout
        synchronized (this) {
            this.wait(15000);
        }

        assertTrue("Test timer expired (15 secs)", passed);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void sendInitialSubscribe() throws ParseException, InvalidArgumentException, SipException {

        // create >From Header
        Address address = addressFactory.createAddress(subscriberSipUri);
        FromHeader fromHeader = headerFactory.createFromHeader(address, Utils.getInstance().generateTag());

        // create To Header
        ToHeader toHeader = headerFactory.createToHeader(address, null);

        // Create ViaHeaders
        ArrayList viaHeaders = new ArrayList();
        int port = sipProvider.getListeningPoint(transport).getPort();
        ViaHeader viaHeader = headerFactory.createViaHeader(ServerConfiguration.SERVER_HOST, port, transport, null);
        viaHeaders.add(viaHeader);

        // Create a new CallId header
        CallIdHeader callIdHeader = sipProvider.getNewCallId();

        // Create a new Cseq header
        CSeqHeader cSeqHeader = headerFactory.createCSeqHeader(1L, Request.SUBSCRIBE);

        // Create a new MaxForwardsHeader
        MaxForwardsHeader maxForwards = headerFactory.createMaxForwardsHeader(70);

        // Create the request.
        Request request = messageFactory.createRequest(address.getURI(), Request.SUBSCRIBE, callIdHeader,
                cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards);

        // Create contact headers
        SipURI contactURI = addressFactory.createSipURI(subscriberUsername, listeningPoint.getIPAddress());
        contactURI.setTransportParam(transport);
        contactURI.setPort(port);
        // add the contact address.
        Address contactAddress = addressFactory.createAddress(contactURI);
        // create and save contact header
        contactHeader = headerFactory.createContactHeader(contactAddress);
        request.addHeader(contactHeader);

        // add route
        request.addHeader(headerFactory.createRouteHeader(addressFactory.createAddress("<sip:"
                + ServerConfiguration.SERVER_HOST + ":" + notifierPort + ";transport=" + transport + ";lr>")));

        // Create an event header for the subscription.
        EventHeader eventHeader = headerFactory.createEventHeader("xcap-diff");
        eventHeader.setParameter("diff-processing", "aggregate");
        request.addHeader(eventHeader);

        // add content
        request.setContent(getTest1XcapContent(),
                headerFactory.createContentTypeHeader("application", "resource-lists+xml"));

        // create the client transaction.
        ClientTransaction clientTransaction = sipProvider.getNewClientTransaction(request);

        // save the dialog
        this.subscriberDialog = clientTransaction.getDialog();

        // send the request out.
        clientTransaction.sendRequest();

    }

    protected XcapDiff processNotify(RequestEvent requestEvent)
            throws ParseException, SipException, InvalidArgumentException, JAXBException {

        Request notify = requestEvent.getRequest();

        javax.sip.message.Response response = messageFactory.createResponse(200, notify);
        // SHOULD add a Contact
        ContactHeader contact = (ContactHeader) contactHeader.clone();
        ((SipURI) contact.getAddress().getURI()).setParameter("id", "sub");
        response.addHeader(contact);
        requestEvent.getServerTransaction().sendResponse(response);

        SubscriptionStateHeader subscriptionState = (SubscriptionStateHeader) requestEvent.getRequest()
                .getHeader(SubscriptionStateHeader.NAME);
        assertTrue("subscription not active",
                subscriptionState.getState().equalsIgnoreCase(SubscriptionStateHeader.ACTIVE));

        // unmarshall content
        StringReader stringReader = new StringReader(new String(requestEvent.getRequest().getRawContent()));
        XcapDiff xcapDiff = (XcapDiff) jaxbContext.createUnmarshaller().unmarshal(stringReader);

        assertTrue("unexpected xcap root in xcap diff",
                xcapDiff.getXcapRoot().equals("http://" + ServerConfiguration.SERVER_HOST + ":"
                        + ServerConfiguration.SERVER_PORT + ServerConfiguration.SERVER_XCAP_ROOT + "/"));
        stringReader.close();

        return xcapDiff;
    }

    /*
     * etag from xcap put response and notify must match.
     */
    protected void processTest1Notify(RequestEvent requestEvent)
            throws JAXBException, ParseException, SipException, InvalidArgumentException {
        XcapDiff xcapDiff = processNotify(requestEvent);
        assertTrue("not a single document element inside xcap diff document received",
                xcapDiff.getDocumentOrElementOrAttribute().size() == 1
                        && xcapDiff.getDocumentOrElementOrAttribute().get(0) instanceof DocumentType);
        DocumentType documentType = (DocumentType) xcapDiff.getDocumentOrElementOrAttribute().get(0);
        assertTrue("doc selector is not " + getDocumentSelector(),
                documentType.getSel() != null && documentType.getSel().equals(getDocumentSelector()));
        assertTrue("previous etag is set",
                documentType.getPreviousEtag() == null || documentType.getPreviousEtag().equals(""));
        assertTrue(
                "new etag (" + documentType.getNewEtag() + ") doesn't match one received in XCAP PUT response ("
                        + newEtag + ")",
                documentType.getNewEtag() != null && documentType.getNewEtag().equals(newEtag));
    }

    // ---- TEST 2

    private void doTest2() throws InterruptedException, HttpException, IOException, URISyntaxException {
        testRunning = Tests.test2;
        sendTest2XcapRequest();
    }

    protected void processTest2Notify(RequestEvent requestEvent)
            throws ParseException, SipException, InvalidArgumentException, JAXBException {
        XcapDiff xcapDiff = processNotify(requestEvent);
        assertTrue("not a single document element inside xcap diff document received",
                xcapDiff.getDocumentOrElementOrAttribute().size() == 1
                        && xcapDiff.getDocumentOrElementOrAttribute().get(0) instanceof DocumentType);
        DocumentType documentType = (DocumentType) xcapDiff.getDocumentOrElementOrAttribute().get(0);
        assertTrue("doc selector is not " + getDocumentSelector(),
                documentType.getSel() != null && documentType.getSel().equals(getDocumentSelector()));
        assertTrue(
                "previous etag (" + documentType.getPreviousEtag()
                        + ") doesn't match one received in first XCAP PUT response (" + previousEtag + ")",
                documentType.getPreviousEtag() != null && documentType.getPreviousEtag().equals(previousEtag));
        assertTrue(
                "new etag (" + documentType.getNewEtag() + ") doesn't match one received in XCAP PUT response ("
                        + newEtag + ")",
                documentType.getNewEtag() != null && documentType.getNewEtag().equals(newEtag));
    }

    // ---- TEST 3

    private void doTest3() throws InterruptedException, HttpException, IOException, URISyntaxException {
        testRunning = Tests.test3;
        sendTest3XcapRequest();
    }

    protected void processTest3Notify(RequestEvent requestEvent)
            throws ParseException, SipException, InvalidArgumentException, JAXBException {
        XcapDiff xcapDiff = processNotify(requestEvent);
        assertTrue("not a single document element inside xcap diff document received",
                xcapDiff.getDocumentOrElementOrAttribute().size() == 1
                        && xcapDiff.getDocumentOrElementOrAttribute().get(0) instanceof DocumentType);
        DocumentType documentType = (DocumentType) xcapDiff.getDocumentOrElementOrAttribute().get(0);
        assertTrue("doc selector is not " + getDocumentSelector(),
                documentType.getSel() != null && documentType.getSel().equals(getDocumentSelector()));
        assertTrue("previous etag doesn't match one received in second XCAP PUT response",
                documentType.getPreviousEtag() != null && documentType.getPreviousEtag().equals(previousEtag));
        assertTrue("new etag is not null", documentType.getNewEtag() == null);
    }

    // ---- TEST 4

    private void doTest4() throws ParseException, SipException, InvalidArgumentException {
        testRunning = Tests.test4;
        unsubscribe();
    }

    private void unsubscribe() throws ParseException, SipException, InvalidArgumentException {

        Request request = this.subscriberDialog.createRequest(Request.SUBSCRIBE);
        ToHeader toHeader = (ToHeader) request.getHeader(ToHeader.NAME);
        if (toHeader.getTag() == null) {
            toHeader.setTag(subscriberToTag);
        }

        // Create a new MaxForwardsHeader
        request.setHeader(headerFactory.createMaxForwardsHeader(70));
        // Create an event header for the subscription.
        EventHeader eventHeader = headerFactory.createEventHeader("xcap-diff");
        eventHeader.setParameter("diff-processing", "aggregate");
        request.addHeader(eventHeader);
        // add expires header
        request.setExpires(headerFactory.createExpiresHeader(0));
        // create client transaction
        ClientTransaction clientTransaction = sipProvider.getNewClientTransaction(request);
        // send request
        clientTransaction.sendRequest();
    }

    protected void processTest4Notify(RequestEvent requestEvent)
            throws ParseException, SipException, InvalidArgumentException, JAXBException {

        Request notify = requestEvent.getRequest();

        javax.sip.message.Response response = messageFactory.createResponse(200, notify);
        // SHOULD add a Contact
        ContactHeader contact = (ContactHeader) contactHeader.clone();
        ((SipURI) contact.getAddress().getURI()).setParameter("id", "sub");
        response.addHeader(contact);
        requestEvent.getServerTransaction().sendResponse(response);

        SubscriptionStateHeader subscriptionState = (SubscriptionStateHeader) requestEvent.getRequest()
                .getHeader(SubscriptionStateHeader.NAME);
        assertTrue("subscription didn't terminate",
                subscriptionState.getState().equalsIgnoreCase(SubscriptionStateHeader.TERMINATED));

    }

    // --- METHODS TO PROCESS SIP REQUESTS AND RESPONSES

    public void processRequest(RequestEvent requestEvent) {

        System.out.println("Request rcvd {\n" + requestEvent.getRequest() + "\n}");

        Request request = requestEvent.getRequest();

        if (request.getMethod().equals(Request.NOTIFY)) {

            try {
                switch (testRunning) {
                case test1:
                    processTest1Notify(requestEvent);
                    semaphore.acquire();
                    doTest2();
                    semaphore.release();
                    break;

                case test2:
                    semaphore.acquire();
                    processTest2Notify(requestEvent);
                    doTest3();
                    semaphore.release();
                    break;

                case test3:
                    processTest3Notify(requestEvent);
                    doTest4();
                    break;

                case test4:
                    processTest4Notify(requestEvent);
                    setTestResult(true);
                    break;

                default:
                    System.err.println("unknown test");
                    setTestResult(false);
                }
            } catch (Exception e) {
                e.printStackTrace();
                setTestResult(false);
            }

        }
    }

    public void processResponse(ResponseEvent responseReceivedEvent) {

        System.out.println("Response received:\n" + responseReceivedEvent.getResponse());
        assertTrue("received response to subscribe which signals that subscription was not approved",
                responseReceivedEvent.getResponse().getStatusCode() == 200);

        if (subscriberToTag == null) {
            subscriberToTag = ((ToHeader) responseReceivedEvent.getResponse().getHeader(ToHeader.NAME)).getTag();
        }
    }

    // --- UNUSED SIP LISTENER METHODS

    public void processDialogTerminated(DialogTerminatedEvent arg0) {
        // TODO Auto-generated method stub

    }

    public void processIOException(IOExceptionEvent arg0) {
        throw new RuntimeException("processIOException(IOExceptionEvent" + arg0 + ")");
    }

    public void processTimeout(TimeoutEvent arg0) {
        throw new RuntimeException("processTimeout(TimeoutEvent=" + arg0 + ")");

    }

    public void processTransactionTerminated(TransactionTerminatedEvent arg0) {
        // TODO Auto-generated method stub

    }

    // ---- TEST setup/cleanup

    @Before
    public void runBefore() throws IOException, InterruptedException, MalformedObjectNameException,
            InstanceNotFoundException, NullPointerException, MBeanException, ReflectionException, NamingException {

        super.user = subscriberSipUri;

        super.runBefore();

        try {
            // init sip stack
            notifierPort = "5060";

            transport = "udp";

            SipFactory sipFactory = SipFactory.getInstance();
            sipFactory.setPathName("gov.nist");
            Properties properties = new Properties();

            properties.setProperty("javax.sip.USE_ROUTER_FOR_ALL_URIS", "false");

            properties.setProperty("javax.sip.STACK_NAME", "subscriber");
            properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "subscriberdebug.txt");
            properties.setProperty("gov.nist.javax.sip.SERVER_LOG", "subscriberlog.txt");

            properties.setProperty("javax.sip.FORKABLE_EVENTS", "foo");

            // Set to 0 in your production code for max speed.
            // You need 16 for logging traces. 32 for debug + traces.
            // Your code will limp at 32 but it is best for debugging.
            properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "0");

            sipStack = sipFactory.createSipStack(properties);
            logger.info("createSipStack " + sipStack);
            headerFactory = sipFactory.createHeaderFactory();
            addressFactory = sipFactory.createAddressFactory();
            messageFactory = sipFactory.createMessageFactory();

            this.listeningPoint = sipStack.createListeningPoint(ServerConfiguration.SERVER_HOST, 6060, transport);
            sipProvider = sipStack.createSipProvider(listeningPoint);
            sipProvider.addSipListener(this);
        } catch (Exception e) {
            throw new RuntimeException("Unable to start sip stack. Exception msg: " + e.getMessage());
        }
    }

    @After
    public void runAfter() throws IOException, InstanceNotFoundException, MBeanException, ReflectionException {

        super.runAfter();

        try {
            sipProvider.removeSipListener(this);
            sipStack.deleteSipProvider(sipProvider);
            sipStack.deleteListeningPoint(listeningPoint);
            sipStack.stop();
        } catch (Exception e) {
            throw new RuntimeException("Unable to stop sip stack. Exception msg: " + e.getMessage());
        }
    }

    private boolean passed = false;

    /**
     * sets test result
     * @param result
     */
    protected void setTestResult(boolean result) {
        passed = result;
        synchronized (this) {

            this.notifyAll();
        }
    }

    // ------ JAXB resource lists and xcap diff context

    protected static final JAXBContext jaxbContext = initJAXBContext();

    private static JAXBContext initJAXBContext() {
        try {
            return JAXBContext.newInstance(
                    "org.openxdm.xcap.common.xcapdiff" + ":org.openxdm.xcap.client.appusage.resourcelists.jaxb");
        } catch (JAXBException e) {
            logger.error("failed to create jaxb context");
            return null;
        }
    }

}