/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.cxf.jaxrs;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;

import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.jaxrs.fortest.BookEntity;
import org.apache.cxf.jaxrs.fortest.BookEntity2;
import org.apache.cxf.jaxrs.fortest.ConcreteRestController;
import org.apache.cxf.jaxrs.fortest.ConcreteRestResource;
import org.apache.cxf.jaxrs.fortest.GenericEntityImpl;
import org.apache.cxf.jaxrs.fortest.GenericEntityImpl2;
import org.apache.cxf.jaxrs.fortest.GenericEntityImpl3;
import org.apache.cxf.jaxrs.fortest.GenericEntityImpl4;
import org.apache.cxf.jaxrs.impl.MetadataMap;
import org.apache.cxf.jaxrs.model.ClassResourceInfo;
import org.apache.cxf.jaxrs.model.OperationResourceInfo;
import org.apache.cxf.jaxrs.provider.ProviderFactory;
import org.apache.cxf.jaxrs.provider.ServerProviderFactory;
import org.apache.cxf.jaxrs.resources.Book;
import org.apache.cxf.jaxrs.resources.Chapter;
import org.apache.cxf.jaxrs.utils.JAXRSUtils;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.ExchangeImpl;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageImpl;
import org.easymock.EasyMock;

import org.junit.Assert;
import org.junit.Test;

public class SelectMethodCandidatesTest extends Assert {
    
    @Test
    public void testFindFromAbstractGenericClass() throws Exception {
        doTestGenericSuperType(BookEntity.class, "POST");
    }
    
    @Test
    public void testFindFromAbstractGenericClass2() throws Exception {
        doTestGenericSuperType(BookEntity2.class, "POST");
    }
    
    @Test
    public void testFindFromAbstractGenericInterface() throws Exception {
        doTestGenericSuperType(GenericEntityImpl.class, "POST");
    }
    
    @Test
    public void testFindFromAbstractGenericInterface2() throws Exception {
        doTestGenericSuperType(GenericEntityImpl2.class, "POST");
    }
    
    @Test
    public void testFindFromAbstractGenericImpl3() throws Exception {
        doTestGenericSuperType(GenericEntityImpl3.class, "POST");
    }
    
    private static List<MediaType> sortMediaTypes(String mediaTypes) {
        return JAXRSUtils.sortMediaTypes(mediaTypes, JAXRSUtils.MEDIA_TYPE_Q_PARAM);
    }
    @Test
    public void testFindFromAbstractGenericImpl4() throws Exception {
        JAXRSServiceFactoryBean sf = new JAXRSServiceFactoryBean();
        sf.setResourceClasses(GenericEntityImpl4.class);
        sf.create();
        List<ClassResourceInfo> resources = ((JAXRSServiceImpl)sf.getService()).getClassResourceInfos();
        String contentTypes = "text/xml";
        String acceptContentTypes = "text/xml";
        
        Message m = new MessageImpl();
        m.put(Message.CONTENT_TYPE, "text/xml");
        Exchange ex = new ExchangeImpl();
        ex.setInMessage(m);
        m.setExchange(ex);
        Endpoint e = EasyMock.createMock(Endpoint.class);
        e.isEmpty();
        EasyMock.expectLastCall().andReturn(true).anyTimes();
        e.size();
        EasyMock.expectLastCall().andReturn(0).anyTimes();
        e.getEndpointInfo();
        EasyMock.expectLastCall().andReturn(null).anyTimes();
        e.get(ServerProviderFactory.class.getName());
        EasyMock.expectLastCall().andReturn(ServerProviderFactory.getInstance()).times(2);
        e.get("org.apache.cxf.jaxrs.comparator");
        EasyMock.expectLastCall().andReturn(null);
        EasyMock.replay(e);
        ex.put(Endpoint.class, e);
        
        MetadataMap<String, String> values = new MetadataMap<String, String>();
        OperationResourceInfo ori = findTargetResourceClass(resources, m, 
                                                            "/books",
                                                            "POST",
                                                            values, contentTypes, 
                                                            sortMediaTypes(acceptContentTypes));
        assertNotNull(ori);
        assertEquals("resourceMethod needs to be selected", "postEntity",
                     ori.getMethodToInvoke().getName());
        
        String value = "<Books><Book><name>The Book</name><id>2</id></Book></Books>";
        m.setContent(InputStream.class, new ByteArrayInputStream(value.getBytes()));
        List<Object> params = JAXRSUtils.processParameters(ori, values, m);
        assertEquals(1, params.size());
        List<?> books = (List<?>)params.get(0);
        assertEquals(1, books.size());
        Book book = (Book)books.get(0);
        assertNotNull(book);
        assertEquals(2L, book.getId());
        assertEquals("The Book", book.getName());
    }
    @Test
    public void testFindFromAbstractGenericImpl5() throws Exception {
        JAXRSServiceFactoryBean sf = new JAXRSServiceFactoryBean();
        sf.setResourceClasses(ConcreteRestController.class);
        sf.create();
        List<ClassResourceInfo> resources = ((JAXRSServiceImpl)sf.getService()).getClassResourceInfos();
        Message m = createMessage();
        m.put(Message.CONTENT_TYPE, "text/xml");
        
        MetadataMap<String, String> values = new MetadataMap<String, String>();
        OperationResourceInfo ori = findTargetResourceClass(resources, m, 
                                                            "/",
                                                            "POST",
                                                            values, "text/xml", 
                                                            sortMediaTypes("*/*"));
        assertNotNull(ori);
        assertEquals("resourceMethod needs to be selected", "add",
                     ori.getMethodToInvoke().getName());
        
        String value = "<concreteRestResource><name>The Book</name></concreteRestResource>";
        m.setContent(InputStream.class, new ByteArrayInputStream(value.getBytes()));
        List<Object> params = JAXRSUtils.processParameters(ori, values, m);
        assertEquals(1, params.size());
        ConcreteRestResource book = (ConcreteRestResource)params.get(0);
        assertNotNull(book);
        assertEquals("The Book", book.getName());
    }
    
    @Test
    public void testFindFromAbstractGenericClass3() throws Exception {
        JAXRSServiceFactoryBean sf = new JAXRSServiceFactoryBean();
        sf.setResourceClasses(BookEntity.class);
        sf.create();
        List<ClassResourceInfo> resources = ((JAXRSServiceImpl)sf.getService()).getClassResourceInfos();
        String contentTypes = "text/xml";
        String acceptContentTypes = "text/xml";
        
        Message m = new MessageImpl();
        m.put(Message.CONTENT_TYPE, "text/xml");
        Exchange ex = new ExchangeImpl();
        ex.setInMessage(m);
        m.setExchange(ex);
        Endpoint e = EasyMock.createMock(Endpoint.class);
        e.isEmpty();
        EasyMock.expectLastCall().andReturn(true).anyTimes();
        e.size();
        EasyMock.expectLastCall().andReturn(0).anyTimes();
        e.getEndpointInfo();
        EasyMock.expectLastCall().andReturn(null).anyTimes();
        e.get(ServerProviderFactory.class.getName());
        EasyMock.expectLastCall().andReturn(ServerProviderFactory.getInstance()).times(3);
        e.get("org.apache.cxf.jaxrs.comparator");
        EasyMock.expectLastCall().andReturn(null);
        EasyMock.replay(e);
        ex.put(Endpoint.class, e);
        
        MetadataMap<String, String> values = new MetadataMap<String, String>();
        OperationResourceInfo ori = findTargetResourceClass(resources, m, 
                                                            "/books",
                                                            "PUT",
                                                            values, contentTypes, 
                                                            sortMediaTypes(acceptContentTypes));
        assertNotNull(ori);
        assertEquals("resourceMethod needs to be selected", "putEntity",
                     ori.getMethodToInvoke().getName());
        
        String value = "<Chapter><title>The Book</title><id>2</id></Chapter>";
        m.setContent(InputStream.class, new ByteArrayInputStream(value.getBytes()));
        List<Object> params = JAXRSUtils.processParameters(ori, values, m);
        assertEquals(1, params.size());
        Chapter c = (Chapter)params.get(0);
        assertNotNull(c);
        assertEquals(2L, c.getId());
        assertEquals("The Book", c.getTitle());
    }
    
    @Test
    public void testDefaultMethod() throws Exception {
        JAXRSServiceFactoryBean sf = new JAXRSServiceFactoryBean();
        sf.setResourceClasses(DefaultMethodResource.class);
        sf.create();
        List<ClassResourceInfo> resources = ((JAXRSServiceImpl)sf.getService()).getClassResourceInfos();
        String contentType = "text/xml";
        String acceptContentTypes = "text/xml";
        
        Message m = new MessageImpl();
        m.put(Message.CONTENT_TYPE, contentType);
        Exchange ex = new ExchangeImpl();
        ex.setInMessage(m);
        m.setExchange(ex);
        Endpoint e = EasyMock.createMock(Endpoint.class);
        e.isEmpty();
        EasyMock.expectLastCall().andReturn(true).anyTimes();
        e.size();
        EasyMock.expectLastCall().andReturn(0).anyTimes();
        e.getEndpointInfo();
        EasyMock.expectLastCall().andReturn(null).anyTimes();
        e.get(ServerProviderFactory.class.getName());
        EasyMock.expectLastCall().andReturn(ServerProviderFactory.getInstance()).times(3);
        e.get("org.apache.cxf.jaxrs.comparator");
        EasyMock.expectLastCall().andReturn(null);
        EasyMock.replay(e);
        ex.put(Endpoint.class, e);
        
        MetadataMap<String, String> values = new MetadataMap<String, String>();
        OperationResourceInfo ori = findTargetResourceClass(resources, m, 
                                                            "/service/all",
                                                            "PUT",
                                                            values, 
                                                            contentType, 
                                                            sortMediaTypes(acceptContentTypes));
        assertNotNull(ori);
        assertEquals("resourceMethod needs to be selected", "all",
                     ori.getMethodToInvoke().getName());
        values.clear();
        ori = findTargetResourceClass(resources, m, 
                                                            "/service/all",
                                                            "GET",
                                                            values, 
                                                            contentType, 
                                                            sortMediaTypes(acceptContentTypes));
        assertNotNull(ori);
        assertEquals("resourceMethod needs to be selected", "getAll",
                     ori.getMethodToInvoke().getName());
        ori = findTargetResourceClass(resources, m, 
                                      "/service",
                                      "GET",
                                      values, 
                                      contentType, 
                                      sortMediaTypes(acceptContentTypes));
        assertNotNull(ori);
        assertEquals("resourceMethod needs to be selected", "get",
            ori.getMethodToInvoke().getName());
    }
    
    @Test
    public void testConsumesResource1() throws Exception {
        doTestConsumesResource(ConsumesResource1.class, "text/xml", "m2");
    }
    @Test
    public void testConsumesResource2() throws Exception {
        doTestConsumesResource(ConsumesResource2.class, "m1");
    }
    @Test
    public void testConsumesResource3() throws Exception {
        doTestConsumesResource(ConsumesResource3.class, "m1");
    }
    @Test
    public void testConsumesResource4() throws Exception {
        doTestConsumesResource(ConsumesResource4.class, "application/xml+bar", "m2");
    }
    
    @Test
    public void testConsumesResource5() throws Exception {
        doTestConsumesResource(ConsumesResource5.class, "text/xml", "m2");
    }

    private void doTestConsumesResource(Class<?> resourceClass, String expectedMethodName) throws Exception {
        doTestConsumesResource(resourceClass, null, expectedMethodName);
    }
    private void doTestConsumesResource(Class<?> resourceClass, String ct, 
                                        String expectedMethodName) throws Exception {
        JAXRSServiceFactoryBean sf = new JAXRSServiceFactoryBean();
        sf.setResourceClasses(resourceClass);
        sf.create();
        List<ClassResourceInfo> resources = ((JAXRSServiceImpl)sf.getService()).getClassResourceInfos();
        String contentType = ct == null ? "application/xml" : ct;
        String acceptContentTypes = "*/*";
        
        Message m = new MessageImpl();
        m.put(Message.CONTENT_TYPE, contentType);
        Exchange ex = new ExchangeImpl();
        ex.setInMessage(m);
        m.setExchange(ex);
        Endpoint e = EasyMock.createMock(Endpoint.class);
        e.isEmpty();
        EasyMock.expectLastCall().andReturn(true).anyTimes();
        e.size();
        EasyMock.expectLastCall().andReturn(0).anyTimes();
        e.getEndpointInfo();
        EasyMock.expectLastCall().andReturn(null).anyTimes();
        e.get(ServerProviderFactory.class.getName());
        EasyMock.expectLastCall().andReturn(ServerProviderFactory.getInstance()).times(2);
        e.get("org.apache.cxf.jaxrs.comparator");
        EasyMock.expectLastCall().andReturn(null);
        EasyMock.replay(e);
        ex.put(Endpoint.class, e);
        
        MetadataMap<String, String> values = new MetadataMap<String, String>();
        OperationResourceInfo ori = findTargetResourceClass(resources, 
                                                            m, 
                                                            "/",
                                                            "POST",
                                                            values, 
                                                            contentType, 
                                                            sortMediaTypes(acceptContentTypes));
        assertNotNull(ori);
        assertEquals(expectedMethodName,  ori.getMethodToInvoke().getName());
    }
    
    @Test
    public void testResponseType1() throws Exception {
        doTestProducesResource(ResponseMediaTypeResource.class, "/m1", 
                               "application/xml", "application/xml", "m1");
    }
    
    @Test
    public void testResponseType2() throws Exception {
        doTestProducesResource(ResponseMediaTypeResource.class, "/m1", 
                               "application/*,application/json", 
                               "application/json", "m1");
    }
    @Test
    public void testResponseType3() throws Exception {
        doTestProducesResource(ResponseMediaTypeResource.class, "/m1", 
                               "text/xml, application/xml;q=0.8,application/json;q=0.4", 
                               "application/xml", "m1");
    }
    
    @Test
    public void testResponseType4() throws Exception {
        doTestProducesResource(ResponseMediaTypeResource.class, "/m1", 
                               "text/xml, application/xml;q=0.8,application/json;q=0.4", 
                               "application/xml", "m1");
    }
    
    @Test
    public void testResponseType5() throws Exception {
        doTestProducesResource(ResponseMediaTypeResource.class, "/m2", 
                               "text/xml, application/xml;q=0.3,application/json;q=0.4", 
                               "application/json", "m2");
    }
    
    @Test
    public void testResponseType6() throws Exception {
        doTestProducesResource(ResponseMediaTypeResource.class, "/m3", 
                               "text/*,application/json;q=0.4", 
                               "text/xml", "m3");
    }
    
    @Test
    public void testResponseType7() throws Exception {
        doTestProducesResource(ResponseMediaTypeResource.class, "/m3", 
                               "text/*,application/json", 
                               "application/json", "m3");
    }
    
    @Test
    public void testResponseType8() throws Exception {
        doTestProducesResource(ResponseMediaTypeResource.class, "/m4", 
                               "application/*", "application/xml", "m4");
    }
    
    @Test
    public void testProducesResource1() throws Exception {
        doTestProducesResource(ProducesResource1.class, "/", 
                               "text/plain, application/xml", 
                               "application/xml", "m2");
    }
    
    @Test
    public void testProducesResource2() throws Exception {
        doTestProducesResource(ProducesResource1.class, "/", 
                               "text/plain, application/xml;q=0.8", 
                               "text/plain", "m1");
    }
    
    @Test
    public void testProducesResource3() throws Exception {
        doTestProducesResource(ProducesResource2.class, "/", 
                               "application/*", 
                               "application/json", "m1");
    }
    
    @Test
    public void testProducesResource4() throws Exception {
        doTestProducesResource(ProducesResource2.class, "/", 
                               "application/xml,application/json;q=0.9", 
                               "application/xml", "m2");
    }
    
    @Test
    public void testProducesResource5() throws Exception {
        doTestProducesResource(ProducesResource2.class, "/", 
                               "application/xml;q=0.3,application/json;q=0.5", 
                               "application/json", "m1");
    }

    @Test
    public void testProducesResource6() throws Exception {
        doTestProducesResource(ProducesResource3.class, "/", 
                               "application/xml,application/json", 
                               "application/xml", "m2");
    }

    @Test
    public void testProducesResource7() throws Exception {
        doTestProducesResource(ProducesResource4.class, "/", 
                               "application/xml,", 
                               "application/xml", "m1");
    }

    @Test
    public void testProducesResource8() throws Exception {
        doTestProducesResource(ProducesResource5.class, "/", 
                               "application/*,text/html", 
                               "text/html", "m1");
    }

    @Test
    public void testProducesResource9() throws Exception {
        doTestProducesResource(ProducesResource5.class, "/", 
                               "application/*,text/html;q=0.3", 
                               "text/html", "m1");
    }

    @Test
    public void testProducesResource10() throws Exception {
        doTestProducesResource(ProducesResource6.class, "/", 
                               "application/*,text/html", 
                               "text/html", "m1");
    }

    private void doTestProducesResource(Class<?> resourceClass, 
                                        String path,
                                        String acceptContentTypes,
                                        String expectedResponseType,
                                        String expectedMethodName) throws Exception {
        JAXRSServiceFactoryBean sf = new JAXRSServiceFactoryBean();
        sf.setResourceClasses(resourceClass);
        sf.create();
        List<ClassResourceInfo> resources = ((JAXRSServiceImpl)sf.getService()).getClassResourceInfos();
        String contentType = "*/*";
        
        Message m = new MessageImpl();
        m.put(Message.CONTENT_TYPE, contentType);
        Exchange ex = new ExchangeImpl();
        ex.setInMessage(m);
        m.setExchange(ex);
        Endpoint e = EasyMock.createMock(Endpoint.class);
        e.isEmpty();
        EasyMock.expectLastCall().andReturn(true).anyTimes();
        e.size();
        EasyMock.expectLastCall().andReturn(0).anyTimes();
        e.getEndpointInfo();
        EasyMock.expectLastCall().andReturn(null).anyTimes();
        e.get(ServerProviderFactory.class.getName());
        EasyMock.expectLastCall().andReturn(ServerProviderFactory.getInstance()).times(2);
        e.get("org.apache.cxf.jaxrs.comparator");
        EasyMock.expectLastCall().andReturn(null);
        EasyMock.replay(e);
        ex.put(Endpoint.class, e);
        
        MetadataMap<String, String> values = new MetadataMap<String, String>();
        Map<ClassResourceInfo, MultivaluedMap<String, String>> mResources 
            = JAXRSUtils.selectResourceClass(resources, path, m);
     
        OperationResourceInfo ori = JAXRSUtils.findTargetMethod(mResources, m, "GET", 
                                                values, contentType, 
                                                sortMediaTypes(acceptContentTypes));
        assertNotNull(ori);
        assertEquals(expectedMethodName,  ori.getMethodToInvoke().getName());
        assertEquals(expectedResponseType, m.getExchange().get(Message.CONTENT_TYPE));
    }
    
    @Test
    public void testRootResourcesWithSameName() throws Exception {
        doTestRootResourcesWithSameName("/a/books", "put", RootResource.class);
        doTestRootResourcesWithSameName("/a1/books", "put", RootResource.class);
    }
    
    @Test
    public void testRootResourcesWithSameName2() throws Exception {
        doTestRootResourcesWithSameName("/a/books/1", "put", RootResource2.class);
        doTestRootResourcesWithSameName("/c/thebooks", "put2", RootResource2.class);
        doTestRootResourcesWithSameName("/b/books", "put", RootResource3.class);
    }
    
    
    private void doTestRootResourcesWithSameName(String path, String methodName, Class<?> expectedRoot) 
        throws Exception {
        JAXRSServiceFactoryBean sf = new JAXRSServiceFactoryBean();
        sf.setResourceClasses(RootResource.class, RootResource2.class, RootResource3.class);
        sf.create();
        List<ClassResourceInfo> resources = ((JAXRSServiceImpl)sf.getService()).getClassResourceInfos();
        assertEquals(3, resources.size());
        String contentTypes = "text/xml";
        String acceptContentTypes = "text/xml";
        
        Message m = prepareMessage();
        
        MetadataMap<String, String> values = new MetadataMap<String, String>();
        OperationResourceInfo ori = findTargetResourceClass(resources, m, 
                                                            path,
                                                            "PUT",
                                                            values, contentTypes, 
                                                            sortMediaTypes(acceptContentTypes));
        assertNotNull(ori);
        assertEquals("resourceMethod needs to be selected", methodName,
                     ori.getMethodToInvoke().getName());
        
        assertSame(expectedRoot, ori.getClassResourceInfo().getServiceClass());
    }
    
    private Message prepareMessage() {
        Message m = new MessageImpl();
        m.put(Message.CONTENT_TYPE, "text/xml");
        Exchange ex = new ExchangeImpl();
        ex.setInMessage(m);
        m.setExchange(ex);
        Endpoint e = EasyMock.createMock(Endpoint.class);
        e.isEmpty();
        EasyMock.expectLastCall().andReturn(true).anyTimes();
        e.size();
        EasyMock.expectLastCall().andReturn(0).anyTimes();
        e.getEndpointInfo();
        EasyMock.expectLastCall().andReturn(null).anyTimes();
        e.get(ServerProviderFactory.class.getName());
        EasyMock.expectLastCall().andReturn(ServerProviderFactory.getInstance()).times(3);
        e.get("org.apache.cxf.jaxrs.comparator");
        EasyMock.expectLastCall().andReturn(null);
        EasyMock.replay(e);
        ex.put(Endpoint.class, e);
        return m;
    }
    
    private void doTestGenericSuperType(Class<?> serviceClass, String methodName) throws Exception {
        JAXRSServiceFactoryBean sf = new JAXRSServiceFactoryBean();
        sf.setResourceClasses(serviceClass);
        sf.create();
        List<ClassResourceInfo> resources = ((JAXRSServiceImpl)sf.getService()).getClassResourceInfos();
        String contentTypes = "text/xml";
        String acceptContentTypes = "text/xml";
        
        Message m = new MessageImpl();
        m.put(Message.CONTENT_TYPE, "text/xml");
        Exchange ex = new ExchangeImpl();
        ex.setInMessage(m);
        m.setExchange(ex);
        Endpoint e = EasyMock.createMock(Endpoint.class);
        e.isEmpty();
        EasyMock.expectLastCall().andReturn(true).anyTimes();
        e.size();
        EasyMock.expectLastCall().andReturn(0).anyTimes();
        e.getEndpointInfo();
        EasyMock.expectLastCall().andReturn(null).anyTimes();
        e.get(ServerProviderFactory.class.getName());
        EasyMock.expectLastCall().andReturn(ServerProviderFactory.getInstance()).times(3);
        e.get("org.apache.cxf.jaxrs.comparator");
        EasyMock.expectLastCall().andReturn(null);
        EasyMock.replay(e);
        ex.put(Endpoint.class, e);
        
        MetadataMap<String, String> values = new MetadataMap<String, String>();
        OperationResourceInfo ori = findTargetResourceClass(resources, m, 
                                                            "/books",
                                                            methodName,
                                                            values, contentTypes, 
                                                            sortMediaTypes(acceptContentTypes));
        assertNotNull(ori);
        assertEquals("resourceMethod needs to be selected", methodName.toLowerCase() + "Entity",
                     ori.getMethodToInvoke().getName());
        
        String value = "<Book><name>The Book</name><id>2</id></Book>";
        m.setContent(InputStream.class, new ByteArrayInputStream(value.getBytes()));
        List<Object> params = JAXRSUtils.processParameters(ori, values, m);
        assertEquals(1, params.size());
        Book book = (Book)params.get(0);
        assertNotNull(book);
        assertEquals(2L, book.getId());
        assertEquals("The Book", book.getName());
    }
    
    @Test
    public void testFindTargetSubResource() throws Exception {
        doTestFindTargetSubResource("/1/2/3/d/resource", "resourceMethod");
    }
    
    @Test
    public void testFindTargetSubResource2() throws Exception {
        doTestFindTargetSubResource("/1/2/3/d/resource/sub", "subresource");
    }
    
    @Test
    public void testFindTargetSubResource3() throws Exception {
        doTestFindTargetSubResource("/1/2/3/d/resource2/2/2", "resourceMethod2");
    }
    
    @Test
    public void testFindTargetSubResource4() throws Exception {
        doTestFindTargetSubResource("/1/2/3/d/resource2/1/2", "subresource2", true);
    }
    @Test
    public void testFindTargetSubResource5() throws Exception {
        doTestFindTargetSubResource("/1/2/3/d/resource2/1/2", "resourceMethod2");
    }
    
    public void doTestFindTargetSubResource(String path, String method) throws Exception {
        doTestFindTargetSubResource(path, method, false);
    }
    
    public void doTestFindTargetSubResource(String path, String method, boolean setKeepSubProp) 
        throws Exception {
        JAXRSServiceFactoryBean sf = new JAXRSServiceFactoryBean();
        sf.setResourceClasses(org.apache.cxf.jaxrs.resources.TestResource.class);
        sf.create();
        List<ClassResourceInfo> resources = ((JAXRSServiceImpl)sf.getService()).getClassResourceInfos();
        String contentTypes = "*/*";
        String acceptContentTypes = "text/xml,*/*";
        
        MetadataMap<String, String> values = new MetadataMap<String, String>();
        OperationResourceInfo ori = findTargetResourceClass(resources, 
                                                            createMessage(), 
                                                            path,
                                                            "GET",
                                                            values, contentTypes, 
                                                            sortMediaTypes(acceptContentTypes),
                                                            setKeepSubProp);
        assertNotNull(ori);
        assertEquals("resourceMethod needs to be selected", method,
                     ori.getMethodToInvoke().getName());
    }
    
    @Test
    public void testSelectUsingQualityFactors() throws Exception {
        JAXRSServiceFactoryBean sf = new JAXRSServiceFactoryBean();
        sf.setResourceClasses(org.apache.cxf.jaxrs.resources.TestResource.class);
        sf.create();
        List<ClassResourceInfo> resources = ((JAXRSServiceImpl)sf.getService()).getClassResourceInfos();
        String contentTypes = "*/*";
        String acceptContentTypes = "application/xml;q=0.5,application/json";
        
        MetadataMap<String, String> values = new MetadataMap<String, String>();
        OperationResourceInfo ori = findTargetResourceClass(resources, createMessage(), 
                                                            "/1/2/3/d/resource1",
                                                            "GET",
                                                            values, contentTypes, 
                                                            sortMediaTypes(acceptContentTypes));
        
        assertNotNull(ori);
        assertEquals("jsonResource needs to be selected", "jsonResource",
                     ori.getMethodToInvoke().getName());
    }
    
    @Test
    public void testFindTargetResourceClassWithTemplates() throws Exception {
        JAXRSServiceFactoryBean sf = new JAXRSServiceFactoryBean();
        sf.setResourceClasses(org.apache.cxf.jaxrs.resources.TestResource.class);
        sf.create();
        
        List<ClassResourceInfo> resources = ((JAXRSServiceImpl)sf.getService()).getClassResourceInfos();

        String contentTypes = "*/*";
        String acceptContentTypes = "application/xml";

        //If acceptContentTypes does not specify a specific Mime type, the  
        //method is declared with a most specific ProduceMime type is selected.
        MetadataMap<String, String> values = new MetadataMap<String, String>();
        OperationResourceInfo ori = findTargetResourceClass(resources, createMessage(), 
                                                            "/1/2/3/d",
                                                            "GET",
                                                            values, contentTypes, 
            Collections.singletonList(MediaType.valueOf(acceptContentTypes)));
        
        assertNotNull(ori);
        assertEquals("listMethod needs to be selected", "listMethod", 
                     ori.getMethodToInvoke().getName());
        
        
        acceptContentTypes = "application/xml,application/json;q=0.8";
        ori = findTargetResourceClass(resources, null, 
                                                            "/1/2/3/d/1",
                                                            "GET",
                                                            values, contentTypes, 
                                                            JAXRSUtils.parseMediaTypes(acceptContentTypes));
        assertNotNull(ori);
        assertEquals("readMethod needs to be selected", "readMethod", 
                     ori.getMethodToInvoke().getName());
        
        
        contentTypes = "application/xml";
        acceptContentTypes = "application/xml";
        ori = findTargetResourceClass(resources, null, 
                                                            "/1/2/3/d/1",
                                                            "GET",
                                                            values, contentTypes, 
            Collections.singletonList(MediaType.valueOf(acceptContentTypes)));
        assertNotNull(ori);
        assertEquals("readMethod needs to be selected", "readMethod", 
                     ori.getMethodToInvoke().getName());
        
        contentTypes = "application/json";
        acceptContentTypes = "application/json";
        ori = findTargetResourceClass(resources, null, 
                                      "/1/2/3/d/1/bar/baz/baz",
                                      "GET",
                                      values, contentTypes, 
            Collections.singletonList(MediaType.valueOf(acceptContentTypes)));
        assertNotNull(ori);
        assertEquals("readMethod2 needs to be selected", "readMethod2", 
                     ori.getMethodToInvoke().getName());
        
        contentTypes = "application/json";
        acceptContentTypes = "application/json";
        ori = findTargetResourceClass(resources, null, 
                                      "/1/2/3/d/1",
                                      "GET",
                                      values, contentTypes, 
            Collections.singletonList(MediaType.valueOf(acceptContentTypes)));
        assertNotNull(ori);
        assertEquals("unlimitedPath needs to be selected", "unlimitedPath", 
                     ori.getMethodToInvoke().getName());
        
        ori = findTargetResourceClass(resources, null, 
                                      "/1/2/3/d/1/2",
                                      "GET",
                                      values, contentTypes, 
            Collections.singletonList(MediaType.valueOf(acceptContentTypes)));
        assertNotNull(ori);
        assertEquals("limitedPath needs to be selected", "limitedPath", 
                     ori.getMethodToInvoke().getName());
        
    }
    
    @Test
    public void testSelectBar() throws Exception {
        JAXRSServiceFactoryBean sf = new JAXRSServiceFactoryBean();
        sf.setResourceClasses(org.apache.cxf.jaxrs.resources.TestResource.class);
        sf.create();
        List<ClassResourceInfo> resources = ((JAXRSServiceImpl)sf.getService()).getClassResourceInfos();
        
        String contentTypes = "*/*";
        String acceptContentTypes = "application/bar,application/foo;q=0.8";
        
        MetadataMap<String, String> values = new MetadataMap<String, String>();
        OperationResourceInfo ori = findTargetResourceClass(resources, null, 
                                      "/1/2/3/d/custom",
                                      "GET",
                                      values, contentTypes, 
                                      sortMediaTypes(acceptContentTypes));
        
        assertNotNull(ori);
        assertEquals("readBar", ori.getMethodToInvoke().getName());
        acceptContentTypes = "application/foo,application/bar;q=0.8";
        ori = findTargetResourceClass(resources, null, 
                                      "/1/2/3/d/custom",
                                      "GET",
                                      values, contentTypes, 
                                      sortMediaTypes(acceptContentTypes));
        assertNotNull(ori);
        assertEquals("readFoo", ori.getMethodToInvoke().getName());
        
        acceptContentTypes = "application/foo;q=0.5,application/bar";
        ori = findTargetResourceClass(resources, null, 
                                      "/1/2/3/d/custom",
                                      "GET",
                                      values, contentTypes, 
                                      sortMediaTypes(acceptContentTypes));
        assertNotNull(ori);
        assertEquals("readBar", ori.getMethodToInvoke().getName());
        
        acceptContentTypes = "application/foo,application/bar;q=0.5";
        ori = findTargetResourceClass(resources, null, 
                                      "/1/2/3/d/custom",
                                      "GET",
                                      values, contentTypes, 
                                      sortMediaTypes(acceptContentTypes));
        assertNotNull(ori);
        assertEquals("readFoo", ori.getMethodToInvoke().getName());
        
    }
    private Message createMessage() {
        return createMessage(false);
    }
    private Message createMessage(boolean setKeepSubProp) {
        ProviderFactory factory = ServerProviderFactory.getInstance();
        Message m = new MessageImpl();
        m.put("org.apache.cxf.http.case_insensitive_queries", false);
        if (setKeepSubProp) {
            m.put("keep.subresource.candidates", true);
        }
        Exchange e = new ExchangeImpl();
        m.setExchange(e);
        e.setInMessage(m);
        Endpoint endpoint = EasyMock.createMock(Endpoint.class);
        endpoint.getEndpointInfo();
        EasyMock.expectLastCall().andReturn(null).anyTimes();
        endpoint.get("org.apache.cxf.jaxrs.comparator");
        EasyMock.expectLastCall().andReturn(null);
        endpoint.size();
        EasyMock.expectLastCall().andReturn(0).anyTimes();
        endpoint.isEmpty();
        EasyMock.expectLastCall().andReturn(true).anyTimes();
        endpoint.get(ServerProviderFactory.class.getName());
        EasyMock.expectLastCall().andReturn(factory).anyTimes();
        EasyMock.replay(endpoint);
        e.put(Endpoint.class, endpoint);
        return m;
    }
    private OperationResourceInfo findTargetResourceClass(List<ClassResourceInfo> resources,
                                                          Message message,
                                                          String path, 
                                                          String httpMethod,
                                                          MultivaluedMap<String, String> values,
                                                          String requestContentType, 
                                                          List<MediaType> acceptContentTypes) {
        return findTargetResourceClass(resources, message, path, httpMethod, values, requestContentType,
                                       acceptContentTypes, false); 
        
    }
    //CHECKSTYLE:OFF
    private OperationResourceInfo findTargetResourceClass(List<ClassResourceInfo> resources,
                                                          Message message,
                                                          String path, 
                                                          String httpMethod,
                                                          MultivaluedMap<String, String> values,
                                                          String requestContentType, 
                                                          List<MediaType> acceptContentTypes,
                                                          boolean setKeepSubProp) {
    //CHECKSTYLE:ON
        message = message == null ? new MessageImpl() : message; 
        Map<ClassResourceInfo, MultivaluedMap<String, String>> mResources 
            = JAXRSUtils.selectResourceClass(resources, path, message);
         
        if (mResources != null) {
            OperationResourceInfo ori = JAXRSUtils.findTargetMethod(mResources, 
                                                                    createMessage(setKeepSubProp), 
                                                                    httpMethod, 
                                                                    values, 
                                                                    requestContentType, 
                                                                    acceptContentTypes);
            if (ori != null) {
                return ori;
            }
        }
         
        return null;
    }
    @Path("{a}")
    @Produces("text/xml")
    @Consumes("text/xml")
    public static class RootResource {
        @PUT
        @Path("books")
        public void put() {
            
        }
    }
    
    @Path("{b}")
    @Produces("text/xml")
    @Consumes("text/xml")
    public static class RootResource2 {
        @PUT
        @Path("books/1")
        public void put() {
            
        }
        
        @PUT
        @Path("thebooks")
        public void put2() {
            
        }
    }
    
    @Path("b")
    @Produces("text/xml")
    @Consumes("text/xml")
    public static class RootResource3 {
        @PUT
        @Path("books")
        public void put() {
            
        }
    }
    
    public static class ConsumesResource1 {
        @POST
        @Consumes({"application/xml", "text/*" })
        public void m1() {
            
        }
        @POST
        @Consumes({"application/xml", "text/xml" })
        public void m2() {
            
        }
    }
    public static class ConsumesResource2 {
        @POST
        @Consumes({"application/*", "text/xml" })
        public void m2() {
            
        }
        @POST
        @Consumes({"text/*", "application/xml" })
        public void m1() {
            
        }
    }
    public static class ConsumesResource3 {
        @POST
        @Consumes({"application/*" })
        public void m2() {
            
        }
        @POST
        @Consumes({"application/xml" })
        public void m1() {
            
        }
    }
    public static class ConsumesResource4 {
        @POST
        @Consumes({"application/xml", "application/xml+*" })
        public void m1() {
            
        }
        @POST
        @Consumes({"application/*", "application/xml+bar" })
        public void m2() {
            
        }
    }
    public static class ConsumesResource5 {
        @POST
        @Consumes("text/*")
        public void m1() {
        }       
        
        @POST
        @Consumes("text/xml;qs=0.7")
        public void m2() {
        }
    }
    
    public static class ResponseMediaTypeResource {
        @GET
        @Path("m1")
        @Produces({"application/json", "application/xml" })
        public Response m1() {
            return null;
        }
        @GET
        @Path("m2")
        @Produces({"application/*" })
        public Response m2() {
            return null;
        }
        @GET
        @Path("m3")
        @Produces({"application/json", "text/xml", "application/xml" })
        public Response m3() {
            return null;
        }
        @GET
        @Path("m4")
        @Produces({"application/json;qs=0.9", "application/xml" })
        public Response m4() {
            return null;
        }
    }
    
    public static class ProducesResource1 {
        @GET
        @Produces({"text/*" })
        public Response m1() {
            return null;
        }
        @GET
        @Produces({"application/xml" })
        public Response m2() {
            return null;
        }
    }
    public static class ProducesResource2 {
        @GET
        @Produces({"application/json" })
        public Response m1() {
            return null;
        }
        @GET
        @Produces({"application/xml;qs=0.9" })
        public Response m2() {
            return null;
        }
    }

    public static class ProducesResource3 {
        @GET
        @Produces({"application/json;qs=0.2" })
        public Response m1() {
            return null;
        }
        @GET
        @Produces({"application/xml;qs=0.9" })
        public Response m2() {
            return null;
        }
    }

    public static class ProducesResource4 {
        @GET
        @Produces({"application/*" })
        public Response m1() {
            return null;
        }
        @GET
        @Produces({"application/xml;qs=0.9" })
        public Response m2() {
            return null;
        }
    }

    public static class ProducesResource5 {
        @GET
        @Produces({"text/*" })
        public Response m1() {
            return null;
        }
        @GET
        @Produces({"application/*" })
        public Response m2() {
            return null;
        }
    }

    public static class ProducesResource6 {
        @GET
        @Produces({"text/*;qs=0.9" })
        public Response m1() {
            return null;
        }
        @GET
        @Produces({"application/*" })
        public Response m2() {
            return null;
        }
    }
}
