/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2010-2013 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * http://glassfish.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package com.sun.jersey.impl.wadl;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.MatrixParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import com.sun.jersey.api.JResponse;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.filter.LoggingFilter;
import com.sun.jersey.api.core.DefaultResourceConfig;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.api.representation.Form;
import com.sun.jersey.api.wadl.config.WadlGeneratorConfig;
import com.sun.jersey.api.wadl.config.WadlGeneratorDescription;
import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.core.header.MediaTypes;
import com.sun.jersey.core.util.SaxHelper;
import com.sun.jersey.impl.AbstractResourceTester;
import com.sun.jersey.impl.entity.JAXBBean;
import com.sun.jersey.server.wadl.WadlApplicationContext;
import com.sun.jersey.server.wadl.WadlGenerator;
import com.sun.jersey.server.wadl.WadlGeneratorImpl;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.sun.research.ws.wadl.Resources;

/**
 * Wadl test cases.
 *
 * @author mh124079
 * @author Michal Gajdos (michal.gajdos at oracle.com)
 */
public class WadlResourceTest extends AbstractResourceTester {

    public WadlResourceTest(String testName) {
        super(testName);
    }

    @Path("foo")
    public static class ExtraResource {
        @GET
        @Produces("application/xml")
        public String getRep() {
            return null;
        }
    }

    @Path("widgets")
    public static class WidgetsResource {

        @GET
        @Produces({"application/xml", "application/json"})
        public String getWidgets() {
            return null;
        }

        @POST
        @Consumes({"application/xml"})
        @Produces({"application/xml", "application/json"})
        public String createWidget(String bar, @MatrixParam("test") String test) {
            return bar;
        }

        @PUT
        @Path("{id}")
        @Consumes("application/xml")
        public void updateWidget(String bar, @PathParam("id")int id) {
        }

        @GET
        @Path("{id}")
        @Produces({"application/xml", "application/json"})
        public String getWidget(@PathParam("id")int id) {
            return null;
        }

        @DELETE
        @Path("{id}")
        public void deleteWidget(@PathParam("id")int id) {
        }

        @Path("{id}/verbose")
        public ExtraResource getVerbose(@PathParam("id")int id) {
            return new ExtraResource();
        }
    }

    public void testDisableWadl() {
        ResourceConfig rc = new DefaultResourceConfig(WidgetsResource.class, ExtraResource.class);
        rc.getFeatures().put(ResourceConfig.FEATURE_DISABLE_WADL, true);
        initiateWebApplication(rc);

        WebResource r = resource("/application.wadl", false);

        ClientResponse cr = r.get(ClientResponse.class);
        assertEquals(404, cr.getStatus());
    }

    public void testEnableWadl() {
        ResourceConfig rc = new DefaultResourceConfig(WidgetsResource.class, ExtraResource.class);
        rc.getFeatures().put(ResourceConfig.FEATURE_DISABLE_WADL, false);
        initiateWebApplication(rc);

        WebResource r = resource("/application.wadl", false);

        ClientResponse cr = r.get(ClientResponse.class);
        assertEquals(200, cr.getStatus());
    }

    /**
     * Test WADL generation.
     *
     * @throws Exception in case of unexpected test failure.
     */
    public void testGetWadl() throws Exception {
        initiateWebApplication(WidgetsResource.class, ExtraResource.class);
        WebResource r = resource("/application.wadl");

        File tmpFile = r.get(File.class);
        DocumentBuilderFactory bf = DocumentBuilderFactory.newInstance();
        bf.setNamespaceAware(true);
        bf.setValidating(false);
        if (!SaxHelper.isXdkDocumentBuilderFactory(bf)) {
            bf.setXIncludeAware(false);
        }
        DocumentBuilder b = bf.newDocumentBuilder();
        Document d = b.parse(tmpFile);
        printSource(new DOMSource(d));
        XPath xp = XPathFactory.newInstance().newXPath();
        xp.setNamespaceContext(new NSResolver("wadl", "http://wadl.dev.java.net/2009/02"));
        // check base URI
        String val = (String)xp.evaluate("/wadl:application/wadl:resources/@base", d, XPathConstants.STRING);
        assertEquals(val,BASE_URI.toString());
        // check total number of resources is 4
        val = (String)xp.evaluate("count(//wadl:resource)", d, XPathConstants.STRING);
        assertEquals(val,"4");
        // check only once resource with for {id}
        val = (String)xp.evaluate("count(//wadl:resource[@path='{id}'])", d, XPathConstants.STRING);
        assertEquals(val,"1");
        // check only once resource with for {id}/verbose
        val = (String)xp.evaluate("count(//wadl:resource[@path='{id}/verbose'])", d, XPathConstants.STRING);
        assertEquals(val,"1");
        // check only once resource with for widgets
        val = (String)xp.evaluate("count(//wadl:resource[@path='widgets'])", d, XPathConstants.STRING);
        assertEquals(val,"1");
        // check 3 methods for {id}
        val = (String)xp.evaluate("count(//wadl:resource[@path='{id}']/wadl:method)", d, XPathConstants.STRING);
        assertEquals(val,"3");
        // check 2 methods for widgets
        val = (String)xp.evaluate("count(//wadl:resource[@path='widgets']/wadl:method)", d, XPathConstants.STRING);
        assertEquals(val,"2");
        // check 1 matrix param on resource
        val = (String)xp.evaluate("count(//wadl:resource[@path='widgets']/wadl:param[@name='test'])", d, XPathConstants.STRING);
        assertEquals(val,"1");
        // check type of {id} is int
        String prefix = getXmlSchemaNamespacePrefix(
                (Node) xp.evaluate("//wadl:resource[@path='{id}']/wadl:param[@name='id']", d, XPathConstants.NODE));
        val = (String)xp.evaluate("//wadl:resource[@path='{id}']/wadl:param[@name='id']/@type", d, XPathConstants.STRING);
        assertEquals(val, (prefix == null ? "" : prefix + ":") + "int");
        // check number of output representations is two
        val = (String)xp.evaluate("count(//wadl:resource[@path='widgets']/wadl:method[@name='GET']/wadl:response/wadl:representation)", d, XPathConstants.STRING);
        assertEquals(val,"2");
        // check number of output representations is one
        val = (String)xp.evaluate("count(//wadl:resource[@path='widgets']/wadl:method[@name='POST']/wadl:request/wadl:representation)", d, XPathConstants.STRING);
        assertEquals(val,"1");
        // check type of {id}/verbose is int
        prefix = getXmlSchemaNamespacePrefix(
                (Node) xp.evaluate("//wadl:resource[@path='{id}/verbose']/wadl:param[@name='id']", d, XPathConstants.NODE));
        val = (String)xp.evaluate("//wadl:resource[@path='{id}/verbose']/wadl:param[@name='id']/@type", d, XPathConstants.STRING);
        assertEquals(val, (prefix == null ? "" : prefix + ":") + "int");
    }

    public void testLastModifiedGET() {
        initiateWebApplication(WidgetsResource.class, ExtraResource.class);
        WebResource r = resource("/application.wadl");

        ClientResponse cr = r.get(ClientResponse.class);
        assertTrue(cr.getHeaders().containsKey("Last-modified"));
    }

    public void testLastModifiedOPTIONS() {
        initiateWebApplication(WidgetsResource.class, ExtraResource.class);
        WebResource r = resource("/widgets/3/verbose");

        ClientResponse cr = r.options(ClientResponse.class);
        assertTrue(cr.getHeaders().containsKey("Last-modified"));
    }


    public void testOptionsResourceWadl() throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
        initiateWebApplication(WidgetsResource.class, ExtraResource.class);
        WebResource r = resource("/widgets");

        // test WidgetsResource
        File tmpFile = r.options(File.class);
        DocumentBuilderFactory bf = DocumentBuilderFactory.newInstance();
        bf.setNamespaceAware(true);
        bf.setValidating(false);
        if (!SaxHelper.isXdkDocumentBuilderFactory(bf)) {
            bf.setXIncludeAware(false);
        }
        DocumentBuilder b = bf.newDocumentBuilder();
        Document d = b.parse(tmpFile);
        printSource(new DOMSource(d));
        XPath xp = XPathFactory.newInstance().newXPath();
        xp.setNamespaceContext(new NSResolver("wadl", "http://wadl.dev.java.net/2009/02"));

        // check base URI
        String val = (String)xp.evaluate("/wadl:application/wadl:resources/@base", d, XPathConstants.STRING);
        assertEquals(val,BASE_URI.toString());
        // check total number of resources is 3 (no ExtraResource details included)
        val = (String)xp.evaluate("count(//wadl:resource)", d, XPathConstants.STRING);
        assertEquals(val,"3");
        // check only once resource with for {id}
        val = (String)xp.evaluate("count(//wadl:resource[@path='{id}'])", d, XPathConstants.STRING);
        assertEquals(val,"1");
        // check only once resource with for {id}/verbose
        val = (String)xp.evaluate("count(//wadl:resource[@path='{id}/verbose'])", d, XPathConstants.STRING);
        assertEquals(val,"1");
        // check only once resource with for widgets
        val = (String)xp.evaluate("count(//wadl:resource[@path='widgets'])", d, XPathConstants.STRING);
        assertEquals(val,"1");
        // check 3 methods for {id}
        val = (String)xp.evaluate("count(//wadl:resource[@path='{id}']/wadl:method)", d, XPathConstants.STRING);
        assertEquals(val,"3");
        // check 2 methods for widgets
        val = (String)xp.evaluate("count(//wadl:resource[@path='widgets']/wadl:method)", d, XPathConstants.STRING);
        assertEquals(val,"2");
        // check type of {id} is int
        String prefix = getXmlSchemaNamespacePrefix(
                (Node) xp.evaluate("//wadl:resource[@path='{id}']/wadl:param[@name='id']", d, XPathConstants.NODE));
        val = (String)xp.evaluate("//wadl:resource[@path='{id}']/wadl:param[@name='id']/@type", d, XPathConstants.STRING);
        assertEquals(val, (prefix == null ? "" : prefix + ":") + "int");
        // check number of output representations is two
        val = (String)xp.evaluate("count(//wadl:resource[@path='widgets']/wadl:method[@name='GET']/wadl:response/wadl:representation)", d, XPathConstants.STRING);
        assertEquals(val,"2");
        // check number of output representations is one
        val = (String)xp.evaluate("count(//wadl:resource[@path='widgets']/wadl:method[@name='POST']/wadl:request/wadl:representation)", d, XPathConstants.STRING);
        assertEquals(val,"1");

        // test ExtraResource
        r = resource("/foo");

        tmpFile = r.options(File.class);
        b = bf.newDocumentBuilder();
        d = b.parse(tmpFile);
        printSource(new DOMSource(d));
        // check base URI
        val = (String)xp.evaluate("/wadl:application/wadl:resources/@base", d, XPathConstants.STRING);
        assertEquals(val,BASE_URI.toString());
        // check total number of resources is 1 (no ExtraResource details included)
        val = (String)xp.evaluate("count(//wadl:resource)", d, XPathConstants.STRING);
        assertEquals(val,"1");
        // check only once resource with path foo
        val = (String)xp.evaluate("count(//wadl:resource[@path='foo'])", d, XPathConstants.STRING);
        assertEquals(val,"1");
        // check 1 methods for foo
        val = (String)xp.evaluate("count(//wadl:resource[@path='foo']/wadl:method)", d, XPathConstants.STRING);
        assertEquals(val,"1");
    }

    private String getXmlSchemaNamespacePrefix(final Node elementNode) {
        final NamedNodeMap attributes = elementNode.getAttributes();

        for (int i = 0; i < attributes.getLength(); i++) {
            final Node item = attributes.item(i);
            if (XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(item.getNodeValue())) {
                return item.getLocalName();
            }
        }

        return "xs";
    }

    public void testOptionsLocatorWadl() throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
        initiateWebApplication(WidgetsResource.class, ExtraResource.class);
        WebResource r = resource("/widgets/3/verbose");

        // test WidgetsResource
        File tmpFile = r.accept(MediaTypes.WADL).options(File.class);
        DocumentBuilderFactory bf = DocumentBuilderFactory.newInstance();
        bf.setNamespaceAware(true);
        bf.setValidating(false);
        if (!SaxHelper.isXdkDocumentBuilderFactory(bf)) {
            bf.setXIncludeAware(false);
        }
        DocumentBuilder b = bf.newDocumentBuilder();
        Document d = b.parse(tmpFile);
        printSource(new DOMSource(d));
        XPath xp = XPathFactory.newInstance().newXPath();
        xp.setNamespaceContext(new NSResolver("wadl", "http://wadl.dev.java.net/2009/02"));
        // check base URI
        String val = (String)xp.evaluate("/wadl:application/wadl:resources/@base", d, XPathConstants.STRING);
        assertEquals(val,BASE_URI.toString());
        // check total number of resources is 1 (no ExtraResource details included)
        val = (String)xp.evaluate("count(//wadl:resource)", d, XPathConstants.STRING);
        assertEquals(val,"1");
        // check only once resource with path
        val = (String)xp.evaluate("count(//wadl:resource[@path='widgets/3/verbose'])", d, XPathConstants.STRING);
        assertEquals(val,"1");
        // check 1 methods
        val = (String)xp.evaluate("count(//wadl:resource[@path='widgets/3/verbose']/wadl:method)", d, XPathConstants.STRING);
        assertEquals(val,"1");
    }

    public void testOptionsSubResourceWadl() throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
        initiateWebApplication(WidgetsResource.class, ExtraResource.class);
        WebResource r = resource("/widgets/3");

        // test WidgetsResource
        File tmpFile = r.accept(MediaTypes.WADL).options(File.class);
        DocumentBuilderFactory bf = DocumentBuilderFactory.newInstance();
        bf.setNamespaceAware(true);
        bf.setValidating(false);
        if (!SaxHelper.isXdkDocumentBuilderFactory(bf)) {
            bf.setXIncludeAware(false);
        }
        DocumentBuilder b = bf.newDocumentBuilder();
        Document d = b.parse(tmpFile);
        printSource(new DOMSource(d));
        XPath xp = XPathFactory.newInstance().newXPath();
        xp.setNamespaceContext(new NSResolver("wadl", "http://wadl.dev.java.net/2009/02"));
        String val = (String)xp.evaluate("/wadl:application/wadl:resources/@base", d, XPathConstants.STRING);
        assertEquals(val,BASE_URI.toString());
        // check total number of resources is 1
        val = (String)xp.evaluate("count(//wadl:resource)", d, XPathConstants.STRING);
        assertEquals(val,"1");
        // check only one resource with for {id}
        val = (String)xp.evaluate("count(//wadl:resource[@path='widgets/3'])", d, XPathConstants.STRING);
        assertEquals(val,"1");
        // check 3 methods
        val = (String)xp.evaluate("count(//wadl:resource[@path='widgets/3']/wadl:method)", d, XPathConstants.STRING);
        assertEquals(val,"3");
    }

    @Path("root")
    public static class RootResource {
        @Path("loc")
        public Object getSub() {
            return new SubResource();
        }

        @Path("switch")
        @POST
        public void switchMethod(@Context WadlApplicationContext wadlApplicationContext) {
            wadlApplicationContext.setWadlGenerationEnabled(!wadlApplicationContext.isWadlGenerationEnabled());

        }
    }

    public static class SubResource {
        @Path("loc")
        public Object getSub() {
            return new SubResource();
        }

        @GET
        @Produces("text/plain")
        public String hello() {
            return "Hello World !";
        }

        @GET
        @Path("sub")
        @Produces("text/plain")
        public String helloSub() {
            return "Hello World !";
        }
    }

    public void testRecursive() throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
        initiateWebApplication(RootResource.class);
        WebResource r = resource("/root/loc");

        // test WidgetsResource
        File tmpFile = r.accept(MediaTypes.WADL).options(File.class);
        DocumentBuilderFactory bf = DocumentBuilderFactory.newInstance();
        bf.setNamespaceAware(true);
        bf.setValidating(false);
        if (!SaxHelper.isXdkDocumentBuilderFactory(bf)) {
            bf.setXIncludeAware(false);
        }
        DocumentBuilder b = bf.newDocumentBuilder();
        Document d = b.parse(tmpFile);
        printSource(new DOMSource(d));
        XPath xp = XPathFactory.newInstance().newXPath();
        xp.setNamespaceContext(new NSResolver("wadl", "http://wadl.dev.java.net/2009/02"));
        String val = (String)xp.evaluate("/wadl:application/wadl:resources/@base", d, XPathConstants.STRING);
        assertEquals(val,BASE_URI.toString());
        // check only one resource with for 'root/loc'
        val = (String)xp.evaluate("count(//wadl:resource[@path='root/loc'])", d, XPathConstants.STRING);
        assertEquals(val,"1");

        r = resource("/root/loc/loc");

        // test WidgetsResource
        tmpFile = r.accept(MediaTypes.WADL).options(File.class);
        bf = DocumentBuilderFactory.newInstance();
        bf.setNamespaceAware(true);
        bf.setValidating(false);
        if (!SaxHelper.isXdkDocumentBuilderFactory(bf)) {
            bf.setXIncludeAware(false);
        }
        b = bf.newDocumentBuilder();
        d = b.parse(tmpFile);
        printSource(new DOMSource(d));
        xp = XPathFactory.newInstance().newXPath();
        xp.setNamespaceContext(new NSResolver("wadl", "http://wadl.dev.java.net/2009/02"));
        val = (String)xp.evaluate("/wadl:application/wadl:resources/@base", d, XPathConstants.STRING);
        assertEquals(val,BASE_URI.toString());
        // check only one resource with for 'root/loc'
        val = (String)xp.evaluate("count(//wadl:resource[@path='root/loc/loc'])", d, XPathConstants.STRING);
        assertEquals(val,"1");

    }

    @Path("root1")
    public static class RootResource1 {
        @Path("loc")
        public SubResource getSub() {
            return new SubResource();
        }
    }

    @Path("root2")
    public static class RootResource2 {
        @Path("loc")
        public SubResource getSub() {
            return new SubResource();
        }
    }

    public void testRecursive2() throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
        initiateWebApplication(RootResource1.class, RootResource2.class);
        WebResource r = resource("/application.wadl");

        File tmpFile = r.get(File.class);
        DocumentBuilderFactory bf = DocumentBuilderFactory.newInstance();
        bf.setNamespaceAware(true);
        bf.setValidating(false);
        if (!SaxHelper.isXdkDocumentBuilderFactory(bf)) {
            bf.setXIncludeAware(false);
        }
        DocumentBuilder b = bf.newDocumentBuilder();
        Document d = b.parse(tmpFile);
        printSource(new DOMSource(d));

        XPath xp = XPathFactory.newInstance().newXPath();
        xp.setNamespaceContext(new NSResolver("wadl", "http://wadl.dev.java.net/2009/02"));
        String val = (String)xp.evaluate("/wadl:application/wadl:resources/@base", d, XPathConstants.STRING);
        assertEquals(val,BASE_URI.toString());
        // check only one resource with for 'root/loc'
        val = (String)xp.evaluate("count(//wadl:resource[@path='loc'])", d, XPathConstants.STRING);
        assertEquals("4", val);
        // check for method with id of hello
        val = (String)xp.evaluate("count(//wadl:resource[@path='loc']/wadl:method[@id='hello'])", d, XPathConstants.STRING);
        assertEquals("2", val);
    }


    @Path("form")
    public static class FormResource {

        @POST
        @Consumes( "application/x-www-form-urlencoded" )
        public void post(
                @FormParam( "a" ) String a,
                @FormParam( "b" ) String b,
                @FormParam( "c" ) JAXBBean c,
                @FormParam( "c" ) FormDataContentDisposition cdc,
                Form form ) {
        }

    }

    public void testFormParam() throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
        initiateWebApplication(FormResource.class);
        WebResource r = resource("/application.wadl");

        File tmpFile = r.get(File.class);
        DocumentBuilderFactory bf = DocumentBuilderFactory.newInstance();
        bf.setNamespaceAware(true);
        bf.setValidating(false);
        if (!SaxHelper.isXdkDocumentBuilderFactory(bf)) {
            bf.setXIncludeAware(false);
        }
        DocumentBuilder b = bf.newDocumentBuilder();
        Document d = b.parse(tmpFile);
        printSource(new DOMSource(d));
        XPath xp = XPathFactory.newInstance().newXPath();
        xp.setNamespaceContext(new NSResolver("wadl", "http://wadl.dev.java.net/2009/02"));

        final String requestPath = "//wadl:resource[@path='form']/wadl:method[@name='POST']/wadl:request";
        final String representationPath = requestPath + "/wadl:representation";

        // check number of request params is zero
        int count = ( (Double)xp.evaluate("count(" + requestPath + "/wadl:param)", d, XPathConstants.NUMBER) ).intValue();
        assertEquals( 0, count );

        // check number of request representations is one
        count = ( (Double)xp.evaluate("count(" + representationPath + ")", d, XPathConstants.NUMBER) ).intValue();
        assertEquals( 1, count );

        // check number of request representation params is three
        count = ( (Double)xp.evaluate("count(" + representationPath + "/wadl:param)", d, XPathConstants.NUMBER) ).intValue();
        assertEquals( 3, count );

        // check the style of the request representation param is 'query'
        String val = (String)xp.evaluate( representationPath + "/wadl:param[@name='a']/@style", d, XPathConstants.STRING);
        assertEquals( "query", val );
        val = (String)xp.evaluate( representationPath + "/wadl:param[@name='b']/@style", d, XPathConstants.STRING);
        assertEquals( "query", val );

    }


    @Path("fieldParam/{pp}")
    public static class FieldParamResource {

        @HeaderParam("hp") String hp;
        @MatrixParam("mp") String mp;
        @PathParam("pp") String pp;
        @QueryParam("q") String q;

        @GET
        @Produces("text/plain" )
        public String get() {
            return pp;
        }

    }

    public void testFieldParam() throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
        _testFieldAndSetterParam(FieldParamResource.class, "fieldParam");
    }

    @Path("setterParam/{pp}")
    public static class SetterParamResource {

        @HeaderParam("hp")
        public void setHp(String hp) {};

        @MatrixParam("mp")
        public void setMp(String mp) {};

        @PathParam("pp")
        public void setPP(String pp) {};

        @QueryParam("q")
        public void setQ(String q) {};

        @GET
        @Produces("text/plain" )
        public String get() {
            return "nonsense";
        }

    }

    public void testSetterParam() throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
        _testFieldAndSetterParam(SetterParamResource.class, "setterParam");
    }

    public void testEnableDisableRuntime() {
        initiateWebApplication(RootResource.class);
        WebResource r = resource("/", false);
        r.addFilter(new LoggingFilter());

        ClientResponse response = r.path("application.wadl").get(ClientResponse.class);
        assertTrue(response.getStatus() == 200);

        response = r.path("root").options(ClientResponse.class);
        assertTrue(response.getStatus() == 200);

        r.path("root/switch").post();

        response = r.path("application.wadl").get(ClientResponse.class);
        assertTrue(response.getStatus() == 404);

        response = r.path("root").options(ClientResponse.class);
        assertTrue(response.getStatus() == 204);

        r.path("root/switch").post();

        response = r.path("application.wadl").get(ClientResponse.class);
        assertTrue(response.getStatus() == 200);

        response = r.path("root").options(ClientResponse.class);
        assertTrue(response.getStatus() == 200);
    }

    private void _testFieldAndSetterParam(Class resourceClass, String path) throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
        initiateWebApplication(resourceClass);
        WebResource r = resource("/application.wadl");

        File tmpFile = r.get(File.class);
        DocumentBuilderFactory bf = DocumentBuilderFactory.newInstance();
        bf.setNamespaceAware(true);
        bf.setValidating(false);
        if (!SaxHelper.isXdkDocumentBuilderFactory(bf)) {
            bf.setXIncludeAware(false);
        }
        DocumentBuilder b = bf.newDocumentBuilder();
        Document d = b.parse(tmpFile);
        printSource(new DOMSource(d));
        XPath xp = XPathFactory.newInstance().newXPath();
        xp.setNamespaceContext(new NSResolver("wadl", "http://wadl.dev.java.net/2009/02"));

        final String resourcePath = String.format("//wadl:resource[@path='%s/{pp}']", path);
        final String methodPath = resourcePath + "/wadl:method[@name='GET']";

        // check number of resource methods is one
        int methodCount = ( (Double)xp.evaluate("count(" + methodPath + ")", d, XPathConstants.NUMBER) ).intValue();
        assertEquals(1, methodCount );

        Map<String, String> paramStyles = new HashMap<String, String>();

        paramStyles.put("hp", "header");
        paramStyles.put("mp", "matrix");
        paramStyles.put("pp", "template");
        paramStyles.put("q", "query");

        for(Entry<String, String> param : paramStyles.entrySet()) {

            String pName = param.getKey();
            String pStyle = param.getValue();

            String paramXPath = String.format("%s/wadl:param[@name='%s']", resourcePath, pName);

            // check number of params is one
            int pc = ( (Double)xp.evaluate("count(" + paramXPath + ")", d, XPathConstants.NUMBER) ).intValue();
            assertEquals(1, pc );

            // check the style of the param
            String style = (String)xp.evaluate(paramXPath + "/@style", d, XPathConstants.STRING);
            assertEquals(pStyle, style );
        }
    }

    private static void printSource(Source source) {
        try {
            System.out.println("---------------------");
            Transformer trans = TransformerFactory.newInstance().newTransformer();
            Properties oprops = new Properties();
            oprops.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
            oprops.put(OutputKeys.INDENT, "yes");
            oprops.put(OutputKeys.METHOD, "xml");
            trans.setOutputProperties(oprops);
            trans.transform(source, new StreamResult(System.out));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static class MyWadlGeneratorConfig extends WadlGeneratorConfig {

        @Override
        public List<WadlGeneratorDescription> configure() {
            return generator(MyWadlGenerator.class).descriptions();
        }

        public static class MyWadlGenerator extends WadlGeneratorImpl {
            public static final String CUSTOM_RESOURCES_BASE_URI = "http://myBaseUri";

            @Override
            public Resources createResources() {
                Resources resources = super.createResources();
                resources.setBase(CUSTOM_RESOURCES_BASE_URI);

                return resources;
            }

            @Override
            public void setWadlGeneratorDelegate(WadlGenerator delegate) {
                // nothing
            }
        }
    }

    /**
     * Test overriding WADL's /application/resources/@base attribute.
     *
     * @throws Exception in case of unexpected test failure.
     */
    public void testCustomWadlResourcesBaseUri() throws Exception {
        ResourceConfig rc = new DefaultResourceConfig(WidgetsResource.class, ExtraResource.class);
        rc.getProperties().put(ResourceConfig.PROPERTY_WADL_GENERATOR_CONFIG, "com.sun.jersey.impl.wadl.WadlResourceTest$MyWadlGeneratorConfig");
        initiateWebApplication(rc);

        WebResource r = resource("/application.wadl");

        File tmpFile = r.get(File.class);
        DocumentBuilderFactory bf = DocumentBuilderFactory.newInstance();
        bf.setNamespaceAware(true);
        bf.setValidating(false);
        if (!SaxHelper.isXdkDocumentBuilderFactory(bf)) {
            bf.setXIncludeAware(false);
        }
        DocumentBuilder b = bf.newDocumentBuilder();
        Document d = b.parse(tmpFile);
        printSource(new DOMSource(d));
        XPath xp = XPathFactory.newInstance().newXPath();
        xp.setNamespaceContext(new NSResolver("wadl", "http://wadl.dev.java.net/2009/02"));
        // check base URI
        String val = (String) xp.evaluate("/wadl:application/wadl:resources/@base", d, XPathConstants.STRING);
        assertEquals(val, MyWadlGeneratorConfig.MyWadlGenerator.CUSTOM_RESOURCES_BASE_URI);
    }

    @Path("jresponse")
    public static class JResponseTestResource {
        @GET
        @Produces("text/plain")
        public JResponse<List<String>> getClichedMessage() {
            // Return some cliched textual content
            return JResponse.<List<String>>status(200).entity(new ArrayList() {
                {
                    add("Hello world!");
                }
            }).build();
        }
    }

    public void testJresponse() throws Exception {
        ResourceConfig rc = new DefaultResourceConfig(JResponseTestResource.class);
        initiateWebApplication(rc);

        WebResource r = resource("/application.wadl");

        assertTrue(r.get(String.class).length() > 0);
    }

    @Path("emptyproduces")
    public static class EmptyProducesTestResource {

        @PUT
        @Produces({})
        public Response put(final String str) {
            return Response.ok().build();
        }

        @POST
        @Path("/sub")
        @Produces({})
        public Response post(final String str) {
            return Response.ok().build();
        }
    }

    public void testEmptyProduces() throws Exception {
        ResourceConfig rc = new DefaultResourceConfig(EmptyProducesTestResource.class);
        initiateWebApplication(rc);

        WebResource r = resource("/application.wadl");

        File tmpFile = r.get(File.class);
        DocumentBuilderFactory bf = DocumentBuilderFactory.newInstance();
        bf.setNamespaceAware(true);
        bf.setValidating(false);
        if (!SaxHelper.isXdkDocumentBuilderFactory(bf)) {
            bf.setXIncludeAware(false);
        }
        DocumentBuilder b = bf.newDocumentBuilder();
        Document d = b.parse(tmpFile);
        printSource(new DOMSource(d));
        XPath xp = XPathFactory.newInstance().newXPath();
        xp.setNamespaceContext(new NSResolver("wadl", "http://wadl.dev.java.net/2009/02"));

        final NodeList responseElements = (NodeList) xp.evaluate(
                "/wadl:application/wadl:resources//wadl:method/wadl:response", d, XPathConstants.NODESET);

        for (int i = 0; i < responseElements.getLength(); i++) {
            final Node item = responseElements.item(i);

            assertEquals(0, item.getChildNodes().getLength());
            assertEquals(0, item.getAttributes().getLength());
        }
    }

    @Path("wadl6test")
    public static class OptionsResource {

        @GET
        @Path("foo1")
        public String foo1() {
            return "foo1";
        }

        @GET
        @Path("/foo2")
        public String foo2() {
            return "foo2";
        }

        @GET
        @Path("foo3/")
        public String foo3() {
            return "foo3";
        }

        @GET
        @Path("/foo4/")
        public String foo4() {
            return "foo4";
        }
    }

    /**
     * Tests OPTIONS method on a resource method annotated with @Path and containing a leading '/'.
     */
    public void testGetWithPathAndLeadingSlash() throws Exception {
        ResourceConfig rc = new DefaultResourceConfig(OptionsResource.class);
        initiateWebApplication(rc);

        for (int i = 1; i < 5; i++) {
            final String[] paths = {
                    "foo" + i,
                    "/foo" + i,
                    "foo" + i + '/',
                    "/foo" + i + '/'
            };

            for (final String path : paths) {
                final String document = resource("wadl6test").
                        path(path).
                        accept("application/vnd.sun.wadl+xml").
                        options(String.class);

                // check that the resulting document contains a method element with id="fooX"
                assertTrue(Pattern.
                        compile(".*<([\\w]+:)*method[^>]+id=\"foo" + i + "\"[^>]*>.*", Pattern.MULTILINE | Pattern.DOTALL).
                        matcher(document).
                        matches());
            }
        }
    }

    @Path(value = "/wadl7test")
    public static class OptionSubResource {

        @GET
        @Path(value = "/sub")
        @Produces(value = "text/html")
        public Response headSub() {
            return Response.ok().build();
        }
    }

    /**
     * On receipt of an OPTIONS request an implementation MUST: Generate an automatic response using the metadata provided by the
     * JAX-RS annotations on the matching class and its methods.
     */
    public void testOptionSub() throws Exception {
        final ResourceConfig resourceConfig = new DefaultResourceConfig(OptionSubResource.class);
        initiateWebApplication(resourceConfig);

        final ClientResponse response = resource("wadl7test").path("sub").accept("text/html").options(ClientResponse.class);
        final Set<String> allow = response.getAllow();

        assertEquals(204, response.getStatus());
        assertEquals(new HashSet<String>(){{ add("GET"); add("OPTIONS"); add("HEAD"); }}, allow);
    }
}
