NaiveResourceLinkContributionContext.java
/*
* Copyright (c) 2017, 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.contributing;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.core.Context;
import org.glassfish.jersey.linking.ProvideLink;
import org.glassfish.jersey.linking.ProvideLinkDescriptor;
import org.glassfish.jersey.linking.ProvideLinks;
import org.glassfish.jersey.server.ExtendedResourceContext;
import org.glassfish.jersey.server.model.HandlerConstructor;
import org.glassfish.jersey.server.model.Invocable;
import org.glassfish.jersey.server.model.MethodHandler;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.server.model.ResourceMethod;
import org.glassfish.jersey.server.model.ResourceModel;
import org.glassfish.jersey.server.model.ResourceModelComponent;
import org.glassfish.jersey.server.model.ResourceModelVisitor;
import org.glassfish.jersey.server.model.RuntimeResource;
/**
* Simple map based implementation of the ResourceLinkContributionContext.
*
* @author Leonard Br��nings
*/
public class NaiveResourceLinkContributionContext implements ResourceLinkContributionContext {
private final ExtendedResourceContext erc;
/**
* Mappings holds a single-level mapping between a class, and it's {@link ProvideLinkDescriptor}s
*/
private Map<Class<?>, List<ProvideLinkDescriptor>> mappings;
/**
* Contributions holds all contributions for a class, this includes all contributions from it's ancestors.
*/
private Map<Class<?>, List<ProvideLinkDescriptor>> contributions = new ConcurrentHashMap<>();
/**
* C'tor
* @param erc the ExtendedResourceContext
*/
public NaiveResourceLinkContributionContext(@Context final ExtendedResourceContext erc) {
this.erc = erc;
}
@Override
public List<ProvideLinkDescriptor> getContributorsFor(Class<?> entityClass) {
buildMappings();
return contributions.computeIfAbsent(entityClass,
aClass -> Collections.unmodifiableList(collectContributors(aClass, new ArrayList<>())));
}
/**
* Collects contributors for a class recursively up the ancestors chain.
*
* @param entityClass the class to
* @param contributors collector list
* @return contributors list for easier use in lambdas
*/
private List<ProvideLinkDescriptor> collectContributors(Class<?> entityClass, List<ProvideLinkDescriptor> contributors) {
contributors.addAll(mappings.getOrDefault(entityClass, Collections.emptyList()));
Class<?> sc = entityClass.getSuperclass();
if (sc != null && sc != Object.class) {
collectContributors(sc, contributors);
}
return contributors;
}
private void buildMappings() {
if (mappings != null) {
return;
}
final Map<Class<?>, List<ProvideLinkDescriptor>> newMappings = new HashMap<>();
erc.getResourceModel().accept(new ResourceModelVisitor() {
private void processComponents(final ResourceModelComponent component) {
final List<? extends ResourceModelComponent> components = component.getComponents();
if (components != null) {
for (final ResourceModelComponent rc : components) {
rc.accept(this);
}
}
}
@Override
public void visitInvocable(final Invocable invocable) {
processComponents(invocable);
}
@Override
public void visitRuntimeResource(final RuntimeResource runtimeResource) {
processComponents(runtimeResource);
}
@Override
public void visitResourceModel(final ResourceModel resourceModel) {
processComponents(resourceModel);
}
@Override
public void visitResourceHandlerConstructor(final HandlerConstructor handlerConstructor) {
processComponents(handlerConstructor);
}
@Override
public void visitMethodHandler(final MethodHandler methodHandler) {
processComponents(methodHandler);
}
@Override
public void visitChildResource(final Resource resource) {
processComponents(resource);
}
@Override
public void visitResource(final Resource resource) {
processComponents(resource);
}
@Override
public void visitResourceMethod(final ResourceMethod resourceMethod) {
if (resourceMethod.isExtended()) {
return;
}
if (resourceMethod.getInvocable() != null) {
final Invocable i = resourceMethod.getInvocable();
Method method = i.getDefinitionMethod();
List<ProvideLinkDescriptor> linkDescriptors = new ArrayList<>();
handleMetaAnnotations(resourceMethod, method, linkDescriptors);
handleAnnotations(resourceMethod, linkDescriptors, method, null);
for (ProvideLinkDescriptor linkDescriptor : linkDescriptors) {
for (Class<?> target : linkDescriptor.getProvideLink().value()) {
target = handleInheritedTarget(linkDescriptor, target);
newMappings.computeIfAbsent(target, aClass -> new ArrayList<>()).add(linkDescriptor);
}
}
}
processComponents(resourceMethod);
}
private Class<?> handleInheritedTarget(ProvideLinkDescriptor linkDescriptor, Class<?> target) {
if (Objects.equals(ProvideLink.InheritFromAnnotation.class, target)) {
Annotation parentAnnotation = linkDescriptor.getParentAnnotation();
if (parentAnnotation == null) {
throw new IllegalArgumentException("InheritFromAnnotation can only be used for Annotations");
}
return findTarget(parentAnnotation);
}
return target;
}
private Class<?> findTarget(Annotation parentAnnotation) {
Method[] methods = parentAnnotation.annotationType().getDeclaredMethods();
for (Method method : methods) {
if (method.isAccessible() || Class.class.isAssignableFrom(method.getReturnType())) {
try {
return (Class<?>) method.invoke(parentAnnotation);
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException ex) {
Logger.getLogger(NaiveResourceLinkContributionContext.class.getName()).log(Level.FINE, null, ex);
}
}
}
throw new IllegalArgumentException("No suitable element of type Class<?> found on: "
+ parentAnnotation.getClass());
}
private void handleMetaAnnotations(ResourceMethod resourceMethod, Method method,
List<ProvideLinkDescriptor> linkDescriptors) {
Annotation[] annotations = method.getDeclaredAnnotations();
for (Annotation annotation : annotations) {
handleAnnotations(resourceMethod, linkDescriptors, annotation.annotationType(), annotation);
}
}
private void handleAnnotations(ResourceMethod resourceMethod, List<ProvideLinkDescriptor> linkDescriptors,
AnnotatedElement element, Annotation parentAnnotation) {
if (element.isAnnotationPresent(ProvideLink.class) || element.isAnnotationPresent(ProvideLinks.class)) {
ProvideLink provideLink = element.getAnnotation(ProvideLink.class);
if (provideLink != null) {
linkDescriptors.add(new ProvideLinkDescriptor(resourceMethod, provideLink, parentAnnotation));
}
ProvideLinks provideLinks = element.getAnnotation(ProvideLinks.class);
if (provideLinks != null) {
for (ProvideLink link : provideLinks.value()) {
linkDescriptors.add(new ProvideLinkDescriptor(resourceMethod, link, parentAnnotation));
}
}
}
}
});
mappings = newMappings;
}
}