FieldProcessorTest.java

/*
 * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.linking;

import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Filter;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.regex.MatchResult;
import java.util.zip.ZipEntry;
import javax.ws.rs.BeanParam;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.UriBuilder;

import javax.xml.bind.annotation.XmlTransient;

import org.glassfish.jersey.internal.util.collection.MultivaluedStringMap;
import org.glassfish.jersey.linking.contributing.ResourceLinkContributionContext;
import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
import org.glassfish.jersey.server.ExtendedUriInfo;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.server.model.ResourceMethod;
import org.glassfish.jersey.server.model.RuntimeResource;
import org.glassfish.jersey.uri.UriTemplate;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * @author Mark Hadley
 * @author Gerard Davison (gerard.davison at oracle.com)
 */
public class FieldProcessorTest {

    private static final Logger LOG = Logger.getLogger(FieldProcessor.class.getName());

    ExtendedUriInfo mockUriInfo = new ExtendedUriInfo() {

        private static final String baseURI = "http://example.com/application/resources";

        private MultivaluedMap queryParams = new MultivaluedStringMap();

        @Override
        public String getPath() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public String getPath(boolean decode) {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public List<PathSegment> getPathSegments() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public List<PathSegment> getPathSegments(boolean decode) {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public URI getRequestUri() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public UriBuilder getRequestUriBuilder() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public URI getAbsolutePath() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public UriBuilder getAbsolutePathBuilder() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public URI getBaseUri() {
            return getBaseUriBuilder().build();
        }

        @Override
        public UriBuilder getBaseUriBuilder() {
            return UriBuilder.fromUri(baseURI);
        }

        @Override
        public MultivaluedMap<String, String> getPathParameters() {
            return new MultivaluedStringMap();
        }

        @Override
        public MultivaluedMap<String, String> getPathParameters(boolean decode) {
            return new MultivaluedStringMap();
        }

        @Override
        public MultivaluedMap<String, String> getQueryParameters() {
            return queryParams;
        }

        @Override
        public MultivaluedMap<String, String> getQueryParameters(boolean decode) {
            return queryParams;
        }

        @Override
        public List<String> getMatchedURIs() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public List<String> getMatchedURIs(boolean decode) {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public List<Object> getMatchedResources() {
            Object dummyResource = new Object() {
            };
            return Collections.singletonList(dummyResource);
        }

        @Override
        public Throwable getMappedThrowable() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public List<MatchResult> getMatchedResults() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public List<UriTemplate> getMatchedTemplates() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public List<PathSegment> getPathSegments(String name) {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public List<PathSegment> getPathSegments(String name, boolean decode) {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public List<RuntimeResource> getMatchedRuntimeResources() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public ResourceMethod getMatchedResourceMethod() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public Resource getMatchedModelResource() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public List<ResourceMethod> getMatchedResourceLocators() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public List<Resource> getLocatorSubResources() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public URI resolve(URI uri) {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public URI relativize(URI uri) {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    };

    private final ResourceMappingContext mockRmc = new ResourceMappingContext() {

        @Override
        public ResourceMappingContext.Mapping getMapping(Class<?> resource) {
            return null;
        }
    };

    protected final ResourceLinkContributionContext mockRlcc = new ResourceLinkContributionContext() {
        @Override
        public List<ProvideLinkDescriptor> getContributorsFor(Class<?> entityClass) {
            return Collections.emptyList();
        }
    };

    private static final String TEMPLATE_A = "foo";

    public static class TestClassD {

        @InjectLink(value = TEMPLATE_A, style = InjectLink.Style.RELATIVE_PATH)
        private String res1;

        @InjectLink(value = TEMPLATE_A, style = InjectLink.Style.RELATIVE_PATH)
        private URI res2;
    }

    @Test
    public void testProcessLinks() {
        LOG.info("Links");

        FieldProcessor<TestClassD> instance = new FieldProcessor(TestClassD.class);
        TestClassD testClass = new TestClassD();
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
        assertEquals(TEMPLATE_A, testClass.res1);
        assertEquals(TEMPLATE_A, testClass.res2.toString());
    }

    private static final String TEMPLATE_B = "widgets/{id}";

    public static class TestClassE {

        @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.RELATIVE_PATH)
        private String link;

        private String id;

        public TestClassE(String id) {
            this.id = id;
        }

        public String getId() {
            return id;
        }
    }

    @Test
    public void testProcessLinksWithFields() {
        LOG.info("Links from field values");
        FieldProcessor<TestClassE> instance = new FieldProcessor(TestClassE.class);
        TestClassE testClass = new TestClassE("10");
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("widgets/10", testClass.link);
    }

    public static class TestClassF {

        @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.RELATIVE_PATH)
        private String thelink;

        private String id;
        private TestClassE nested;

        public TestClassF(String id, TestClassE e) {
            this.id = id;
            this.nested = e;
        }

        public String getId() {
            return id;
        }
    }

    @Test
    public void testNesting() {
        LOG.info("Nesting");
        FieldProcessor<TestClassF> instance = new FieldProcessor(TestClassF.class);
        TestClassE nested = new TestClassE("10");
        TestClassF testClass = new TestClassF("20", nested);
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("widgets/20", testClass.thelink);
        assertEquals("widgets/10", testClass.nested.link);
    }

    @Test
    public void testArray() {
        LOG.info("Array");
        FieldProcessor<TestClassE[]> instance = new FieldProcessor(TestClassE[].class);
        TestClassE item1 = new TestClassE("10");
        TestClassE item2 = new TestClassE("20");
        TestClassE array[] = {item1, item2};
        instance.processLinks(array, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("widgets/10", array[0].link);
        assertEquals("widgets/20", array[1].link);
    }

    @Test
    public void testCollection() {
        LOG.info("Collection");
        FieldProcessor<List> instance = new FieldProcessor(List.class);
        TestClassE item1 = new TestClassE("10");
        TestClassE item2 = new TestClassE("20");
        List<TestClassE> list = Arrays.asList(item1, item2);
        instance.processLinks(list, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("widgets/10", list.get(0).link);
        assertEquals("widgets/20", list.get(1).link);
    }

    @Test
    public void testMap() {
        LOG.info("Map");
        FieldProcessor<Map> instance = new FieldProcessor(Map.class);
        TestClassE item1 = new TestClassE("10");
        TestClassE item2 = new TestClassE("20");
        Map<String, TestClassE> map = new HashMap<>();
        for (TestClassE item : Arrays.asList(item1, item2)) {
            map.put(item.getId(), item);
        }
        instance.processLinks(map, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("widgets/10", map.get("10").link);
        assertEquals("widgets/20", map.get("20").link);
    }

    public static class TestClassG {

        @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.RELATIVE_PATH)
        private String relativePath;

        @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.ABSOLUTE_PATH)
        private String absolutePath;

        @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.ABSOLUTE)
        private String absolute;

        @InjectLink(TEMPLATE_B)
        private String defaultStyle;

        private String id;

        public TestClassG(String id) {
            this.id = id;
        }

        public String getId() {
            return id;
        }
    }

    @Test
    public void testLinkStyles() {
        LOG.info("Link styles");
        FieldProcessor<TestClassG> instance = new FieldProcessor(TestClassG.class);
        TestClassG testClass = new TestClassG("10");
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("widgets/10", testClass.relativePath);
        assertEquals("/application/resources/widgets/10", testClass.absolutePath);
        assertEquals("/application/resources/widgets/10", testClass.defaultStyle);
        assertEquals("http://example.com/application/resources/widgets/10", testClass.absolute);
    }

    public static class TestClassH {

        @InjectLink(TEMPLATE_B)
        private String link;

        public String getId() {
            return "10";
        }
    }

    @Test
    public void testComputedProperty() {
        LOG.info("Computed property");
        FieldProcessor<TestClassH> instance = new FieldProcessor(TestClassH.class);
        TestClassH testClass = new TestClassH();
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("/application/resources/widgets/10", testClass.link);
    }

    public static class TestClassI {

        @InjectLink("widgets/${entity.id}")
        private String link;

        public String getId() {
            return "10";
        }
    }

    @Test
    public void testEL() {
        LOG.info("El link");
        FieldProcessor<TestClassI> instance = new FieldProcessor(TestClassI.class);
        TestClassI testClass = new TestClassI();
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("/application/resources/widgets/10", testClass.link);
    }

    public static class TestClassJ {

        @InjectLink("widgets/${entity.id}/widget/{id}")
        private String link;

        public String getId() {
            return "10";
        }
    }

    @Test
    public void testMixed() {
        LOG.info("Mixed EL and template vars link");
        FieldProcessor<TestClassJ> instance = new FieldProcessor(TestClassJ.class);
        TestClassJ testClass = new TestClassJ();
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("/application/resources/widgets/10/widget/10", testClass.link);
    }

    public static class DependentInnerBean {

        @InjectLink("${entity.id}")
        public String outerUri;
        @InjectLink("${instance.id}")
        public String innerUri;

        public String getId() {
            return "inner";
        }
    }

    public static class OuterBean {

        public DependentInnerBean inner = new DependentInnerBean();

        public String getId() {
            return "outer";
        }
    }

    @Test
    public void testELScopes() {
        LOG.info("EL scopes");
        FieldProcessor<OuterBean> instance = new FieldProcessor(OuterBean.class);
        OuterBean testClass = new OuterBean();
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("/application/resources/inner", testClass.inner.innerUri);
        assertEquals("/application/resources/outer", testClass.inner.outerUri);
    }

    public static class BoundLinkBean {

        @InjectLink(value = "{id}", bindings = {@Binding(name = "id", value = "${instance.name}")})
        public String uri;

        public String getName() {
            return "name";
        }
    }

    @Test
    public void testELBinding() {
        LOG.info("EL binding");
        FieldProcessor<BoundLinkBean> instance = new FieldProcessor(BoundLinkBean.class);
        BoundLinkBean testClass = new BoundLinkBean();
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("/application/resources/name", testClass.uri);
    }

    public static class BoundLinkOnLinkBean {

        @InjectLink(value = "{id}",
                bindings = {@Binding(name = "id", value = "${instance.name}")},
                rel = "self")
        public Link link;

        public String getName() {
            return "name";
        }
    }

    @Test
    public void testELBindingOnLink() {
        LOG.info("EL binding");
        FieldProcessor<BoundLinkOnLinkBean> instance = new FieldProcessor(BoundLinkOnLinkBean.class);
        BoundLinkOnLinkBean testClass = new BoundLinkOnLinkBean();
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("/application/resources/name", testClass.link.getUri().toString());
        assertEquals("self", testClass.link.getRel());
    }

    public static class BoundLinkOnLinksBean {

        @InjectLinks({
                @InjectLink(value = "{id}",
                        bindings = {@Binding(name = "id", value = "${instance.name}")},
                        rel = "self"),
                @InjectLink(value = "{id}",
                        bindings = {@Binding(name = "id", value = "${instance.name}")},
                        rel = "other"),

        })
        public List<Link> links;

        @InjectLinks({
                @InjectLink(value = "{id}",
                        bindings = {@Binding(name = "id", value = "${instance.name}")},
                        rel = "self"),
                @InjectLink(value = "{id}",
                        bindings = {@Binding(name = "id", value = "${instance.name}")},
                        rel = "other"),

        })
        public Link[] linksArray;

        public String getName() {
            return "name";
        }
    }

    @Test
    public void testELBindingOnLinks() {
        LOG.info("EL binding");
        FieldProcessor<BoundLinkOnLinksBean> instance = new FieldProcessor(BoundLinkOnLinksBean.class);
        BoundLinkOnLinksBean testClass = new BoundLinkOnLinksBean();
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("/application/resources/name", testClass.links.get(0).getUri().toString());
        assertEquals("self", testClass.links.get(0).getRel());
        assertEquals("other", testClass.links.get(1).getRel());

        assertEquals("/application/resources/name", testClass.linksArray[0].getUri().toString());
        assertEquals("self", testClass.linksArray[0].getRel());
        assertEquals("other", testClass.linksArray[1].getRel());

    }

    public static class ConditionalLinkBean {

        @InjectLink(value = "{id}", condition = "${entity.uri1Enabled}")
        public String uri1;

        @InjectLink(value = "{id}", condition = "${entity.uri2Enabled}")
        public String uri2;

        public String getId() {
            return "name";
        }

        public boolean isUri1Enabled() {
            return true;
        }

        public boolean isUri2Enabled() {
            return false;
        }
    }

    @Test
    public void testCondition() {
        LOG.info("Condition");
        FieldProcessor<ConditionalLinkBean> instance = new FieldProcessor(ConditionalLinkBean.class);
        ConditionalLinkBean testClass = new ConditionalLinkBean();
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("/application/resources/name", testClass.uri1);
        assertEquals(null, testClass.uri2);
    }

    @Path("a")
    public static class SubResource {

        @Path("b")
        @GET
        public String getB() {
            return "hello world";
        }
    }

    public static class SubResourceBean {

        @InjectLink(resource = SubResource.class, method = "getB")
        public String uri;
    }

    @Test
    public void testSubresource() {
        LOG.info("Subresource");
        FieldProcessor<SubResourceBean> instance = new FieldProcessor(SubResourceBean.class);
        SubResourceBean testClass = new SubResourceBean();
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("/application/resources/a/b", testClass.uri);
    }

    @Path("a")
    public static class QueryResource {

        @Path("b")
        @GET
        public String getB(@QueryParam("query") String query, @QueryParam("query2") String query2) {
            return "hello world";
        }
    }

    public static class QueryResourceBean {

        public String getQueryParam() {
            return queryExample;
        }

        private String queryExample;

        public QueryResourceBean(String queryExample, String queryExample2) {
            this.queryExample = queryExample;
            this.queryExample2 = queryExample2;
        }

        public String getQueryParam2() {
            return queryExample2;
        }

        private String queryExample2;

        @InjectLink(resource = QueryResource.class, method = "getB",
                bindings = {
                        @Binding(name = "query", value = "${instance.queryParam}"),
                        @Binding(name = "query2", value = "${instance.queryParam2}")
                })
        public String uri;
    }

    public static class QueryResourceBeanNoBindings {
        //query parameters will be populated from uriInfo
        //JERSEY-2863
        @InjectLink(resource = QueryResource.class, method = "getB")
        public String uri;
    }

    @Test
    public void testQueryResource() {
        LOG.info("QueryResource");
        FieldProcessor<QueryResourceBean> instance = new FieldProcessor(QueryResourceBean.class);
        QueryResourceBean testClass = new QueryResourceBean("queryExample", null);
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("/application/resources/a/b?query=queryExample&query2=", testClass.uri);
    }

    @Test
    public void testDoubleQueryResource() {
        LOG.info("QueryResource");
        FieldProcessor<QueryResourceBean> instance = new FieldProcessor(QueryResourceBean.class);
        QueryResourceBean testClass = new QueryResourceBean("queryExample", "queryExample2");
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("/application/resources/a/b?query=queryExample&query2=queryExample2", testClass.uri);
    }

    @Test
    public void testQueryResourceWithoutBindings() {
        LOG.info("QueryResource");
        FieldProcessor<QueryResourceBeanNoBindings> instance = new FieldProcessor(QueryResourceBeanNoBindings.class);
        QueryResourceBeanNoBindings testClass = new QueryResourceBeanNoBindings();
        mockUriInfo.getQueryParameters().putSingle("query", "queryExample");
        mockUriInfo.getQueryParameters().putSingle("query2", "queryExample2");
        assertEquals("queryExample", mockUriInfo.getQueryParameters().getFirst("query"));
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("/application/resources/a/b?query=queryExample&query2=queryExample2", testClass.uri);
        //clean mock
        mockUriInfo.getQueryParameters().clear();
    }

    /** Bean param with method setter QueryParam. */
    public static class BeanParamBeanA {
        private String qparam;
        @QueryParam("qparam")
        public void setQParam(String qparam) {
            this.qparam = qparam;
        }
    }

    /** Bean param with field QueryParam. */
    public static class BeanParamBeanB {
        @QueryParam("query") public String query;
    }

    @Path("a")
    public static class BeanParamQueryResource {

        @Path("b")
        @GET
        public String getB(@BeanParam BeanParamBeanA beanParamBeanA, @BeanParam BeanParamBeanB beanParamBeanB) {
            return "hello world";
        }
    }

    public static class BeanParamResourceBean {

        public String getQueryParam() {
            return queryExample;
        }

        private String queryExample;

        public BeanParamResourceBean(String queryExample) {
            this.queryExample = queryExample;
        }

        @InjectLink(resource = BeanParamQueryResource.class, method = "getB",
                bindings = {
                        @Binding(name = "query", value = "${instance.queryParam}"),
                        @Binding(name = "qparam", value = "foo")
                })
        public String uri;
    }

    @Test
    public void testBeanParamResource() {
        LOG.info("BeanParamResource");
        FieldProcessor<BeanParamResourceBean> instance = new FieldProcessor(BeanParamResourceBean.class);
        BeanParamResourceBean testClass = new BeanParamResourceBean("queryExample");
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("/application/resources/a/b?qparam=foo&query=queryExample", testClass.uri);
    }

    public static class TestClassK {

        public static final ZipEntry zipEntry = new ZipEntry("entry");
    }

    public static class TestClassL {

        public final ZipEntry zipEntry = new ZipEntry("entry");
    }

    private class LoggingFilter implements Filter {

        private int count = 0;

        @Override
        public synchronized boolean isLoggable(LogRecord logRecord) {
            if (logRecord.getThrown() instanceof IllegalAccessException) {
                count++;
                return false;
            }
            return true;
        }

        public int getCount() {
            return count;
        }
    }

    @Test
    public void testKL() {
        final LoggingFilter lf = new LoggingFilter();

        Logger.getLogger(FieldDescriptor.class.getName()).setFilter(lf);
        assertTrue(lf.getCount() == 0);

        FieldProcessor<TestClassK> instanceK = new FieldProcessor(TestClassK.class);
        TestClassK testClassK = new TestClassK();
        instanceK.processLinks(testClassK, mockUriInfo, mockRmc, mockRlcc);

        assertTrue(lf.getCount() == 0);

        FieldProcessor<TestClassL> instanceL = new FieldProcessor(TestClassL.class);
        TestClassL testClassL = new TestClassL();
        instanceL.processLinks(testClassL, mockUriInfo, mockRmc, mockRlcc);

        assertTrue(lf.getCount() == 0);

        Logger.getLogger(FieldDescriptor.class.getName()).setFilter(null);

    }

    public static class TestClassM {

        @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.RELATIVE_PATH)
        private String thelink;

        private String id;

        @InjectLinkNoFollow
        private TestClassE nested;

        @XmlTransient
        private TestClassE transientNested;

        public TestClassM(String id, TestClassE e, TestClassE transientNested) {
            this.id = id;
            this.nested = e;
            this.transientNested = transientNested;
        }

        public String getId() {
            return id;
        }
    }

    @Test
    public void testNoRecursiveNesting() {
        LOG.info("No Recursive Nesting");
        FieldProcessor<TestClassM> instance = new FieldProcessor(TestClassM.class);
        TestClassE nested = new TestClassE("10");
        TestClassE transientNested = new TestClassE("30");
        TestClassM testClass = new TestClassM("20", nested, transientNested);
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
        assertEquals("widgets/20", testClass.thelink);
        assertEquals(null, testClass.nested.link);
        assertEquals(null, testClass.transientNested.link);
    }

    public static class TestClassN {
        // Simulate object injected by JPA
        // in order to test a fix for JERSEY-2625
        private transient Iterable res1 = new Iterable() {
            @Override
            public Iterator iterator() {
                throw new RuntimeException("Declarative linking feature is incorrectly processing a transient iterator");
            }

        };
    }

    @Test
    public void testIgnoreTransient() {
        TestClassN testClass = new TestClassN();
        FieldProcessor<TestClassN> instance = new FieldProcessor(TestClassN.class);
        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
    }

}