blob: e0afebaec1739f5c85857e25087f35f394370d77 [file] [log] [blame]
/*
* Copyright (c) 2010, 2018 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.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.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);
}
}